# HG changeset patch # User Christian Brabandt # Date 1711619106 -3600 # Node ID 5b25ec43f20873ecb9cd61e3f2337894b3c742c6 # Parent 42bca55140ec05920158deab5134e4e66547c5f2 patch 9.1.0219: Vim9: No enum support Commit: https://github.com/vim/vim/commit/3164cf8f12f14b725b918e3170bb0a9085af8298 Author: Yegappan Lakshmanan Date: Thu Mar 28 10:36:42 2024 +0100 patch 9.1.0219: Vim9: No enum support Problem: No enum support Solution: Implement enums for Vim9 script (Yegappan Lakshmanan) closes: #14224 Signed-off-by: Yegappan Lakshmanan Signed-off-by: Christian Brabandt diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1,4 +1,4 @@ -*builtin.txt* For Vim version 9.1. Last change: 2024 Mar 23 +*builtin.txt* For Vim version 9.1. Last change: 2024 Mar 28 VIM REFERENCE MANUAL by Bram Moolenaar @@ -9598,6 +9598,8 @@ string({expr}) Return {expr} converted t Dictionary {key: value, key: value} Class class SomeName Object object of SomeName {lnum: 1, col: 3} + Enum enum EnumName + EnumValue enum.value When a |List| or |Dictionary| has a recursive reference it is replaced by "[...]" or "{...}". Using eval() on the result @@ -10461,6 +10463,8 @@ type({expr}) The result is a Number repr Class: 12 |v:t_class| Object: 13 |v:t_object| Typealias: 14 |v:t_typealias| + Enum: 15 |v:t_enum| + EnumValue: 16 |v:t_enumvalue| For backward compatibility, this method can be used: > :if type(myvar) == type(0) :if type(myvar) == type("") diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 9.1. Last change: 2024 Mar 20 +*eval.txt* For Vim version 9.1. Last change: 2024 Mar 28 VIM REFERENCE MANUAL by Bram Moolenaar @@ -2601,6 +2601,10 @@ v:t_class Value of |class| type. Read-o v:t_object Value of |object| type. Read-only. See: |type()| *v:t_typealias* *t_typealias-variable* v:t_typealias Value of |typealias| type. Read-only. See: |type()| + *v:t_enum* *t_enum-variable* +v:t_enum Value of |enum| type. Read-only. See: |type()| + *v:t_enumvalue* *t_enumvalue-variable* +v:t_enumvalue Value of |enumvalue| type. Read-only. See: |type()| *v:termresponse* *termresponse-variable* v:termresponse The escape sequence returned by the terminal for the |t_RV| diff --git a/runtime/doc/tags b/runtime/doc/tags --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -4520,7 +4520,20 @@ E1410 vim9class.txt /*E1410* E1411 vim9class.txt /*E1411* E1412 vim9class.txt /*E1412* E1413 vim9class.txt /*E1413* +E1414 vim9class.txt /*E1414* +E1415 vim9class.txt /*E1415* +E1416 vim9class.txt /*E1416* +E1417 vim9class.txt /*E1417* +E1418 vim9class.txt /*E1418* +E1419 vim9class.txt /*E1419* E142 message.txt /*E142* +E1420 vim9class.txt /*E1420* +E1421 vim9class.txt /*E1421* +E1422 vim9class.txt /*E1422* +E1423 vim9class.txt /*E1423* +E1424 vim9class.txt /*E1424* +E1425 vim9class.txt /*E1425* +E1426 vim9class.txt /*E1426* E143 autocmd.txt /*E143* E144 various.txt /*E144* E145 starting.txt /*E145* @@ -6874,6 +6887,12 @@ encryption editing.txt /*encryption* end intro.txt /*end* end-of-file pattern.txt /*end-of-file* enlightened-terminal syntax.txt /*enlightened-terminal* +enum vim9class.txt /*enum* +enum-constructor vim9class.txt /*enum-constructor* +enum-name vim9class.txt /*enum-name* +enum-ordinal vim9class.txt /*enum-ordinal* +enum-values vim9class.txt /*enum-values* +enumvalue vim9class.txt /*enumvalue* environ() builtin.txt /*environ()* eol-and-eof editing.txt /*eol-and-eof* erlang.vim syntax.txt /*erlang.vim* @@ -10290,6 +10309,8 @@ t_dl term.txt /*t_dl* t_ds term.txt /*t_ds* t_ed version4.txt /*t_ed* t_el version4.txt /*t_el* +t_enum-variable eval.txt /*t_enum-variable* +t_enumvalue-variable eval.txt /*t_enumvalue-variable* t_f1 version4.txt /*t_f1* t_f10 version4.txt /*t_f10* t_f2 version4.txt /*t_f2* @@ -10863,6 +10884,8 @@ v:t_bool eval.txt /*v:t_bool* v:t_channel eval.txt /*v:t_channel* v:t_class eval.txt /*v:t_class* v:t_dict eval.txt /*v:t_dict* +v:t_enum eval.txt /*v:t_enum* +v:t_enumvalue eval.txt /*v:t_enumvalue* v:t_float eval.txt /*v:t_float* v:t_func eval.txt /*v:t_func* v:t_job eval.txt /*v:t_job* 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.1. Last change: 2024 Mar 03 +*todo.txt* For Vim version 9.1. Last change: 2024 Mar 28 VIM REFERENCE MANUAL by Bram Moolenaar @@ -144,7 +144,6 @@ Further Vim9 improvements: - More efficient way for interface member index than iterating over list? - a variant of type() that returns a different type for each class? list and list should also differ. -- implement :enum - Promise class, could be used to wait on a popup close callback? - class local to a function - Use Vim9 for more runtime files. diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -41548,6 +41548,8 @@ Vim9 script Add support for internal builtin functions with vim9 objects, see |builtin-object-methods| +Enum support for Vim9 script |:enum| + Other improvements *new-other-9.2* ------------------ diff --git a/runtime/doc/vim9class.txt b/runtime/doc/vim9class.txt --- a/runtime/doc/vim9class.txt +++ b/runtime/doc/vim9class.txt @@ -1,4 +1,4 @@ -*vim9class.txt* For Vim version 9.1. Last change: 2024 Mar 03 +*vim9class.txt* For Vim version 9.1. Last change: 2024 Mar 28 VIM REFERENCE MANUAL by Bram Moolenaar @@ -904,19 +904,125 @@ aliased: > 8. Enum *Vim9-enum* *:enum* *:endenum* -{not implemented yet} - + *enum* *E1418* *E1419* *E1420* An enum is a type that can have one of a list of values. Example: > - :enum Color - White - Red - Green - Blue - Black - :endenum + :enum Color + White, + Red, + Green, Blue, Black + :endenum +< + *enumvalue* *E1422* +The enum values are separated by commas. More than one enum value can be +listed in a single line. The final enum value should not be followed by a +comma. + +An enum value is accessed using the enum name followed by the value name: > + + var a: Color = Color.Blue +< +Enums are treated as classes, where each enum value is essentially an instance +of that class. Unlike typical object instantiation with the |new()| method, +enum instances cannot be created this way. + +An enum can only be defined in a |Vim9| script file. *E1414* +An enum cannot be defined inside a function. + + *E1415* +An enum name must start with an uppercase letter. The name of an enum value +in an enum can start with an upper or lowercase letter. + + *E1416* +An enum can implement an interface but cannot extend a class: > + + enum MyEnum implements MyIntf + Value1, + Value2 + + def SomeMethod() + enddef + endenum +< + *enum-constructor* +The enum value objects in an enum are constructed like any other objects using +the |new()| method. Arguments can be passed to the enum constructor by +specifying them after the enum value name, just like calling a function. The +default constructor doesn't have any arguments. + + *E1417* +An enum can contain class variables, class methods, object variables and +object methods. The methods in an enum cannot be |:abstract| methods. + +The following example shows an enum with object variables and methods: > + + vim9script + enum Planet + Earth(1, false), + Jupiter(95, true), + Saturn(146, true) + var moons: number + var has_rings: bool + def GetMoons(): number + return this.moons + enddef + endenum + echo Planet.Jupiter.GetMoons() + echo Planet.Earth.has_rings +< + *E1421* *E1423* *E1424* *E1425* +Enums and their values are immutable. They cannot be modified after +declaration and cannot be utilized as numerical or string types. + *enum-name* +Each enum value object has a "name" instance variable which contains the name +of the enum value. This is a readonly variable. + + *enum-ordinal* *E1426* +Each enum value has an associated ordinal number starting with 0. The ordinal +number of an enum value can be accessed using the "ordinal" instance variable. +This is a readonly variable. Note that if the ordering of the enum values in +an enum is changed, then their ordinal values will also change. + + *enum-values* +All the values in an enum can be accessed using the "values" class variable +which is a List of the enum objects. This is a readonly variable. + +Example: > + enum Planet + Mercury, + Venus, + Earth + endenum + + echo Planet.Mercury + echo Planet.Venus.name + echo Planet.Venus.ordinal + for p in Planet.values + # ... + endfor +< +An enum is a class with class variables for the enum value objects and object +variables for the enum value name and the enum value ordinal: > + + enum Planet + Mercury, + Venus + endenum +< +The above enum definition is equivalent to the following class definition: > + + class Planet + public static final Mercury: Planet = Planet.new('Mercury', 0) + public static final Venus: Planet = Planet.new('Venus', 1) + + public static const values: list = [Planet.Mercury, Planet.Venus] + + public const name: string + public const ordinal: number + endclass +< ============================================================================== 9. Rationale diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3585,8 +3585,38 @@ EXTERN char e_builtin_object_method_str_ INIT(= N_("E1412: Builtin object method \"%s\" not supported")); EXTERN char e_builtin_class_method_not_supported[] INIT(= N_("E1413: Builtin class method not supported")); -#endif -// E1415 - E1499 unused (reserved for Vim9 class support) +EXTERN char e_enum_can_only_be_defined_in_vim9_script[] + INIT(= N_("E1414: Enum can only be defined in Vim9 script")); +EXTERN char e_enum_name_must_start_with_uppercase_letter_str[] + INIT(= N_("E1415: Enum name must start with an uppercase letter: %s")); +EXTERN char e_enum_cannot_extend_class[] + INIT(= N_("E1416: Enum cannot extend a class or enum")); +EXTERN char e_abstract_cannot_be_used_in_enum[] + INIT(= N_("E1417: Abstract cannot be used in an Enum")); +EXTERN char e_invalid_enum_value_declaration_str[] + INIT(= N_("E1418: Invalid enum value declaration: %s")); +EXTERN char e_not_valid_command_in_enum_str[] + INIT(= N_("E1419: Not a valid command in an Enum: %s")); +EXTERN char e_missing_endenum[] + INIT(= N_("E1420: Missing :endenum")); +EXTERN char e_using_enum_as_value_str[] + INIT(= N_("E1421: Enum \"%s\" cannot be used as a value")); +EXTERN char e_enum_value_str_not_found_in_enum_str[] + INIT(= N_("E1422: Enum value \"%s\" not found in enum \"%s\"")); +EXTERN char e_enumvalue_str_cannot_be_modified[] + INIT(= N_("E1423: Enum value \"%s.%s\" cannot be modified")); +EXTERN char e_using_enum_str_as_number[] + INIT(= N_("E1424: Using an Enum \"%s\" as a Number")); +EXTERN char e_using_enum_str_as_string[] + INIT(= N_("E1425: Using an Enum \"%s\" as a String")); +EXTERN char e_enum_str_ordinal_cannot_be_modified[] + INIT(= N_("E1426: Enum \"%s\" ordinal value cannot be modified")); +EXTERN char e_enum_str_name_cannot_be_modified[] + INIT(= N_("E1427: Enum \"%s\" name cannot be modified")); +EXTERN char e_duplicate_enum_str[] + INIT(= N_("E1428: Duplicate enum value: %s")); +#endif +// E1429 - 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 @@ -1119,7 +1119,18 @@ get_lval_check_access( if (*p == '[' || *p == '.') break; if ((flags & GLV_READ_ONLY) == 0) - msg = e_variable_is_not_writable_str; + { + if (IS_ENUM(cl)) + { + if (om->ocm_type->tt_type == VAR_OBJECT) + semsg(_(e_enumvalue_str_cannot_be_modified), + cl->class_name, om->ocm_name); + else + msg = e_variable_is_not_writable_str; + } + else + msg = e_variable_is_not_writable_str; + } break; case VIM_ACCESS_ALL: break; @@ -6310,9 +6321,15 @@ echo_string_core( case VAR_CLASS: { class_T *cl = tv->vval.v_class; - size_t len = 6 + (cl == NULL ? 9 : STRLEN(cl->class_name)) + 1; + char *s = "class"; + if (IS_INTERFACE(cl)) + s = "interface"; + else if (IS_ENUM(cl)) + s = "enum"; + size_t len = STRLEN(s) + 1 + + (cl == NULL ? 9 : STRLEN(cl->class_name)) + 1; r = *tofree = alloc(len); - vim_snprintf((char *)r, len, "class %s", + vim_snprintf((char *)r, len, "%s %s", s, cl == NULL ? "[unknown]" : (char *)cl->class_name); } break; diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -11486,15 +11486,31 @@ f_type(typval_T *argvars, typval_T *rett case VAR_CHANNEL: n = VAR_TYPE_CHANNEL; break; case VAR_BLOB: n = VAR_TYPE_BLOB; break; case VAR_INSTR: n = VAR_TYPE_INSTR; break; - case VAR_CLASS: n = VAR_TYPE_CLASS; break; - case VAR_OBJECT: n = VAR_TYPE_OBJECT; break; case VAR_TYPEALIAS: n = VAR_TYPE_TYPEALIAS; break; + case VAR_CLASS: + { + class_T *cl = argvars[0].vval.v_class; + if (IS_ENUM(cl)) + n = VAR_TYPE_ENUM; + else + n = VAR_TYPE_CLASS; + break; + } + case VAR_OBJECT: + { + class_T *cl = argvars[0].vval.v_object->obj_class; + if (IS_ENUM(cl)) + n = VAR_TYPE_ENUMVALUE; + else + n = VAR_TYPE_OBJECT; + break; + } case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: - internal_error_no_abort("f_type(UNKNOWN)"); - n = -1; - break; + internal_error_no_abort("f_type(UNKNOWN)"); + n = -1; + break; } rettv->vval.v_number = n; } diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -159,6 +159,8 @@ static struct vimvar {VV_NAME("maxcol", VAR_NUMBER), NULL, VV_RO}, {VV_NAME("python3_version", VAR_NUMBER), NULL, VV_RO}, {VV_NAME("t_typealias", VAR_NUMBER), NULL, VV_RO}, + {VV_NAME("t_enum", VAR_NUMBER), NULL, VV_RO}, + {VV_NAME("t_enumvalue", VAR_NUMBER), NULL, VV_RO}, }; // shorthand @@ -262,6 +264,8 @@ evalvars_init(void) set_vim_var_nr(VV_TYPE_CLASS, VAR_TYPE_CLASS); set_vim_var_nr(VV_TYPE_OBJECT, VAR_TYPE_OBJECT); set_vim_var_nr(VV_TYPE_TYPEALIAS, VAR_TYPE_TYPEALIAS); + set_vim_var_nr(VV_TYPE_ENUM, VAR_TYPE_ENUM); + set_vim_var_nr(VV_TYPE_ENUMVALUE, VAR_TYPE_ENUMVALUE); set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); diff --git a/src/ex_cmds.h b/src/ex_cmds.h --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -595,7 +595,7 @@ EXCMD(CMD_endwhile, "endwhile", ex_endwh EXCMD(CMD_enew, "enew", ex_edit, EX_BANG|EX_TRLBAR, ADDR_NONE), -EXCMD(CMD_enum, "enum", ex_enum, +EXCMD(CMD_enum, "enum", ex_class, EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK|EX_EXPORT, ADDR_NONE), EXCMD(CMD_eval, "eval", ex_eval, diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -3,6 +3,7 @@ int object_index_from_itf_index(class_T int is_valid_builtin_obj_methodname(char_u *funcname); ufunc_T *class_get_builtin_method(class_T *cl, class_builtin_T builtin_method, int *method_idx); void ex_class(exarg_T *eap); +void enum_set_internal_obj_vars(class_T *en, object_T *enval); type_T *oc_member_type(class_T *cl, int is_object, char_u *name, char_u *name_end, int *member_idx); type_T *oc_member_type_by_idx(class_T *cl, int is_object, int member_idx); void ex_enum(exarg_T *eap); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1562,9 +1562,10 @@ struct itf2class_S { // array with ints follows }; -#define CLASS_INTERFACE 1 -#define CLASS_EXTENDED 2 // another class extends this one -#define CLASS_ABSTRACT 4 // abstract class +#define CLASS_INTERFACE 0x1 +#define CLASS_EXTENDED 0x2 // another class extends this one +#define CLASS_ABSTRACT 0x4 // abstract class +#define CLASS_ENUM 0x8 // enum // "class_T": used for v_class of typval of VAR_CLASS // Also used for an interface (class_flags has CLASS_INTERFACE). @@ -1613,6 +1614,9 @@ struct class_S type_T class_object_type; // same as class_type but VAR_OBJECT }; +#define IS_INTERFACE(cl) ((cl)->class_flags & CLASS_INTERFACE) +#define IS_ENUM(cl) ((cl)->class_flags & CLASS_ENUM) + // Used for v_object of typval of VAR_OBJECT. // The member variables follow in an array of typval_T. struct object_S diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -40,6 +40,7 @@ TEST_VIM9 = \ test_vim9_class \ test_vim9_cmd \ test_vim9_disassemble \ + test_vim9_enum \ test_vim9_expr \ test_vim9_fails \ test_vim9_func \ @@ -53,6 +54,7 @@ TEST_VIM9_RES = \ test_vim9_class.res \ test_vim9_cmd.res \ test_vim9_disassemble.res \ + test_vim9_enum.res \ test_vim9_expr.res \ test_vim9_fails.res \ test_vim9_func.res \ 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 @@ -2275,6 +2275,18 @@ def Test_interface_basics() v9.CheckScriptSuccess(lines) enddef +" Test for using string() with an interface +def Test_interface_to_string() + var lines =<< trim END + vim9script + interface Intf + def Method(nr: number) + endinterface + assert_equal("interface Intf", string(Intf)) + END + v9.CheckSourceSuccess(lines) +enddef + def Test_class_implements_interface() var lines =<< trim END vim9script @@ -10391,6 +10403,23 @@ def Test_compound_op_in_objmethod_lambda v9.CheckScriptSuccess(lines) enddef +" Test for using test_refcount() with a class and an object +def Test_class_object_refcount() + var lines =<< trim END + vim9script + class A + endclass + var a: A = A.new() + assert_equal(2, test_refcount(A)) + assert_equal(1, test_refcount(a)) + var b = a + assert_equal(2, test_refcount(A)) + assert_equal(2, test_refcount(a)) + assert_equal(2, test_refcount(b)) + END + v9.CheckScriptSuccess(lines) +enddef + " call a lambda function in one object from another object def Test_lambda_invocation_across_classes() var lines =<< trim END @@ -10420,4 +10449,18 @@ def Test_lambda_invocation_across_classe v9.CheckScriptSuccess(lines) enddef +" Test for using a class member which is an object of the current class +def Test_current_class_object_class_member() + var lines =<< trim END + vim9script + class A + public static var obj1: A = A.new(10) + var n: number + endclass + defcompile + assert_equal(10, A.obj1.n) + END + v9.CheckScriptSuccess(lines) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_enum.vim b/src/testdir/test_vim9_enum.vim new file mode 100644 --- /dev/null +++ b/src/testdir/test_vim9_enum.vim @@ -0,0 +1,1476 @@ +" Test Vim9 enums + +source check.vim +import './vim9.vim' as v9 + +" Test for parsing an enum definition +def Test_enum_parse() + # enum supported only in a Vim9 script + var lines =<< trim END + enum Foo + endenum + END + v9.CheckSourceFailure(lines, 'E1414: Enum can only be defined in Vim9 script', 1) + + # First character in an enum name should be capitalized. + lines =<< trim END + vim9script + enum foo + endenum + END + v9.CheckSourceFailure(lines, 'E1415: Enum name must start with an uppercase letter: foo', 2) + + # Only alphanumeric characters are supported in an enum name + lines =<< trim END + vim9script + enum Foo@bar + endenum + END + v9.CheckSourceFailure(lines, 'E1315: White space required after name: Foo@bar', 2) + + # Unsupported keyword (instead of enum) + lines =<< trim END + vim9script + noenum Something + endenum + END + v9.CheckSourceFailure(lines, 'E492: Not an editor command: noenum Something', 2) + + # Only the complete word "enum" should be recognized + lines =<< trim END + vim9script + enums Something + endenum + END + v9.CheckSourceFailure(lines, 'E492: Not an editor command: enums Something', 2) + + # The complete "endenum" should be specified. + lines =<< trim END + vim9script + enum Something + enden + END + v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: enden', 3) + + # Only the complete word "endenum" should be recognized + lines =<< trim END + vim9script + enum Something + endenums + END + v9.CheckSourceFailure(lines, 'E1420: Missing :endenum', 4) + + # all lower case should be used for "enum" + lines =<< trim END + vim9script + Enum Something + endenum + END + v9.CheckSourceFailure(lines, 'E492: Not an editor command: Enum Something', 2) + + # all lower case should be used for "endenum" + lines =<< trim END + vim9script + enum Something + Endenum + END + v9.CheckSourceFailure(lines, 'E1420: Missing :endenum', 4) + + # Additional words after "endenum" + lines =<< trim END + vim9script + enum Something + endenum school's out + END + v9.CheckSourceFailure(lines, "E488: Trailing characters: school's out", 3) + + # Additional commands after "endenum" + lines =<< trim END + vim9script + enum Something + endenum | echo 'done' + END + v9.CheckSourceFailure(lines, "E488: Trailing characters: | echo 'done'", 3) + + # Try to define enum in a single command + lines =<< trim END + vim9script + enum Something | endenum + END + v9.CheckSourceFailure(lines, 'E1420: Missing :endenum', 3) + + # Try to define an enum with the same name as an existing variable + lines =<< trim END + vim9script + var Something: list = [1] + enum Something + endenum + END + v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "Something"', 3) + + # Unsupported special character following enum name + lines =<< trim END + vim9script + enum Foo + first, + second : 20 + endenum + END + v9.CheckSourceFailure(lines, 'E1123: Missing comma before argument: : 20', 4) + + # Try initializing an enum item with a number + lines =<< trim END + vim9script + enum Foo + first, + second = 2 + endenum + END + v9.CheckSourceFailure(lines, 'E1123: Missing comma before argument: = 2', 4) + + # Try initializing an enum item with a String + lines =<< trim END + vim9script + enum Foo + first, + second = 'second' + endenum + defcompile + END + v9.CheckSourceFailure(lines, "E1123: Missing comma before argument: = 'second'", 4) + + # Try initializing an enum item with a List + lines =<< trim END + vim9script + enum Foo + first, + second = [] + endenum + END + v9.CheckSourceFailure(lines, 'E1123: Missing comma before argument: = []', 4) + + # Use a colon after name + lines =<< trim END + vim9script + enum Foo + + # first + first: + second + endenum + END + v9.CheckSourceFailure(lines, 'E1123: Missing comma before argument: :', 5) + + # Use a '==' + lines =<< trim END + vim9script + enum Foo + first == 1 + endenum + defcompile + END + v9.CheckSourceFailure(lines, 'E1123: Missing comma before argument: == 1', 3) + + # Missing comma after an enum item + lines =<< trim END + vim9script + enum Planet + mercury + venus + endenum + END + v9.CheckSourceFailure(lines, 'E1419: Not a valid command in an Enum: venus', 4) + + # Comma at the beginning of an item + lines =<< trim END + vim9script + enum Planet + mercury + ,venus + endenum + END + v9.CheckSourceFailure(lines, 'E1419: Not a valid command in an Enum: ,venus', 4) + # Space before comma + lines =<< trim END + vim9script + enum Planet + mercury , + venus + endenum + END + v9.CheckSourceFailure(lines, "E1068: No white space allowed before ','", 3) + + # No space after comma + lines =<< trim END + vim9script + enum Planet + mercury,venus + endenum + END + v9.CheckSourceFailure(lines, "E1069: White space required after ',': mercury,venus", 3) + + # no comma between items in the same line + lines =<< trim END + vim9script + enum Planet + mercury venus earth + endenum + defcompile + END + v9.CheckSourceFailure(lines, 'E1123: Missing comma before argument: venus earth', 3) + + # No space after an item and comment between items + lines =<< trim END + vim9script + enum Planet + mercury + + # Venus + venus + endenum + END + v9.CheckSourceFailure(lines, 'E1419: Not a valid command in an Enum: venus', 6) + + # Comma is supported for the last item + lines =<< trim END + vim9script + enum Planet + mercury, + venus, + endenum + var p: Planet + p = Planet.mercury + p = Planet.venus + END + v9.CheckSourceSuccess(lines) + + # invalid enum value declaration + lines =<< trim END + vim9script + enum Fruit + Apple, + $%@ + endenum + defcompile + END + v9.CheckSourceFailure(lines, 'E1418: Invalid enum value declaration: $%@', 4) + + # Duplicate enum value + lines =<< trim END + vim9script + enum A + Foo, + Bar, + Foo + endenum + END + v9.CheckSourceFailure(lines, 'E1428: Duplicate enum value: Foo', 5) + + # Duplicate enum value in the same line + lines =<< trim END + vim9script + enum A + Foo, Bar, Foo, + Bar + endenum + END + v9.CheckSourceFailure(lines, 'E1428: Duplicate enum value: Foo', 3) + + # Try extending a class when defining an enum + lines =<< trim END + vim9script + class Foo + endclass + enum Bar extends Foo + endenum + END + v9.CheckSourceFailure(lines, 'E1416: Enum cannot extend a class or enum', 4) + + # Try extending an enum + lines =<< trim END + vim9script + enum Foo + endenum + enum Bar extends Foo + endenum + END + v9.CheckSourceFailure(lines, 'E1416: Enum cannot extend a class or enum', 4) + + # Try extending an enum using a class + lines =<< trim END + vim9script + enum Foo + endenum + class Bar extends Foo + endclass + END + v9.CheckSourceFailure(lines, 'E1354: Cannot extend Foo', 5) + + # Try implementing an enum using a class + lines =<< trim END + vim9script + enum Foo + endenum + class Bar implements Foo + endclass + END + v9.CheckSourceFailure(lines, 'E1347: Not a valid interface: Foo', 5) + + # abstract method is not supported in an enum + lines =<< trim END + vim9script + enum Foo + Apple + abstract def Bar() + endenum + END + v9.CheckSourceFailure(lines, 'E1417: Abstract cannot be used in an Enum', 4) + + # Define an enum without any enum values but only with an object variable + lines =<< trim END + vim9script + enum Foo + final n: number = 10 + endenum + END + v9.CheckSourceFailure(lines, 'E1123: Missing comma before argument: n: number = 10', 3) +enddef + +def Test_basic_enum() + # Declare a simple enum + var lines =<< trim END + vim9script + enum Foo + apple, + orange + endenum + var a: Foo = Foo.apple + var b: Foo = Foo.orange + assert_equal(a, Foo.apple) + assert_equal(b, Foo.orange) + END + v9.CheckSourceSuccess(lines) + + # Multiple enums in a single line + lines =<< trim END + vim9script + enum Foo + apple, orange + endenum + assert_equal('enum', typename(Foo.apple)) + assert_equal('enum', typename(Foo.orange)) + END + v9.CheckSourceSuccess(lines) + + # Comments and empty lines are supported between enum items + lines =<< trim END + vim9script + enum Foo + # Apple + apple, + + # Orange + orange + endenum + def Fn() + var a: Foo = Foo.apple + var b: Foo = Foo.orange + assert_equal(a, Foo.apple) + assert_equal(b, Foo.orange) + enddef + END + v9.CheckSourceSuccess(lines) + + # Try using a non-existing enum value + lines =<< trim END + vim9script + enum Foo + apple, + orange + endenum + var a: Foo = Foo.pear + END + v9.CheckSourceFailure(lines, 'E1422: Enum value "pear" not found in enum "Foo"', 6) + + # Enum function argument + lines =<< trim END + vim9script + enum Foo + apple, + orange + endenum + def Fn(a: Foo): Foo + return a + enddef + assert_equal(Foo.apple, Fn(Foo.apple)) + END + v9.CheckSourceSuccess(lines) + + # Enum function argument + lines =<< trim END + vim9script + enum Foo + apple, + orange + endenum + def Fn(a: Foo): Foo + return a + enddef + Fn({}) + END + v9.CheckSourceFailure(lines, 'E1013: Argument 1: type mismatch, expected enum but got dict', 9) + + # Returning an enum in a function returning number + lines =<< trim END + vim9script + enum Foo + apple, + orange + endenum + def Fn(): number + return Foo.orange + enddef + Fn() + END + v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected number but got enum', 1) + + # Returning a number in a function returning enum + lines =<< trim END + vim9script + enum Foo + apple, + orange + endenum + def Fn(): Foo + return 20 + enddef + Fn() + END + v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected enum but got number', 1) + + # Use a List of enums + lines =<< trim END + vim9script + enum Planet + Mercury, + Venus, + Earth + endenum + var l1: list = [Planet.Mercury, Planet.Venus] + assert_equal(Planet.Venus, l1[1]) + def Fn() + var l2: list = [Planet.Mercury, Planet.Venus] + assert_equal(Planet.Venus, l2[1]) + enddef + END + v9.CheckSourceSuccess(lines) + + # Try using an enum as a value + lines =<< trim END + vim9script + enum Fruit + Apple, + Orange + endenum + var a = Fruit + END + v9.CheckSourceFailure(lines, 'E1421: Enum "Fruit" cannot be used as a value', 6) +enddef + +" Test for type() and typename() of an enum +def Test_enum_type() + var lines =<< trim END + vim9script + enum Fruit + Apple, + Orange + endenum + assert_equal('enum', typename(Fruit)) + assert_equal('enum', typename(Fruit.Apple)) + assert_equal(v:t_enum, type(Fruit)) + assert_equal(v:t_enumvalue, type(Fruit.Apple)) + assert_equal(15, type(Fruit)) + assert_equal(16, type(Fruit.Apple)) + END + v9.CheckSourceSuccess(lines) + + # Assign an enum to a variable with any type + lines =<< trim END + vim9script + enum Fruit + Apple, + Orange + endenum + var a = Fruit.Apple + var b: any = Fruit.Orange + assert_equal('enum', typename(a)) + assert_equal('enum', typename(b)) + END + v9.CheckSourceSuccess(lines) +enddef + +" Try modifying an enum or an enum item +def Test_enum_modify() + # Try assigning an unsupported value to an enum + var lines =<< trim END + vim9script + enum Foo + apple + endenum + var a: Foo = 30 + END + v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected enum but got number', 5) + + # Try assigning an unsupported value to an enum in a function + lines =<< trim END + vim9script + enum Foo + apple + endenum + def Fn() + var a: Foo = 30 + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected enum but got number', 1) + + # Try assigning a number to an enum + lines =<< trim END + vim9script + enum Foo + apple, + orange + endenum + Foo = 10 + END + v9.CheckSourceFailure(lines, 'E1421: Enum "Foo" cannot be used as a value', 6) + + # Try assigning a number to an enum in a function + lines =<< trim END + vim9script + enum Foo + apple + endenum + def Fn() + Foo = 10 + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected enum but got number', 1) + + # Try assigning a number to an enum value + lines =<< trim END + vim9script + enum Foo + apple + endenum + Foo.apple = 20 + END + v9.CheckSourceFailure(lines, 'E1423: Enum value "Foo.apple" cannot be modified', 5) + + # Try assigning a number to an enum value in a function + lines =<< trim END + vim9script + enum Foo + apple + endenum + def Fn() + Foo.apple = 20 + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1423: Enum value "Foo.apple" cannot be modified', 1) + + # Try assigning one enum to another + lines =<< trim END + vim9script + enum Foo + endenum + enum Bar + endenum + Foo = Bar + END + v9.CheckSourceFailure(lines, 'E1421: Enum "Bar" cannot be used as a value', 6) + + # Try assigning one enum to another in a function + lines =<< trim END + vim9script + enum Foo + endenum + enum Bar + endenum + def Fn() + Foo = Bar + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1421: Enum "Bar" cannot be used as a value', 1) + + # Try assigning one enum item to another enum item + lines =<< trim END + vim9script + enum Foo + Apple + endenum + enum Bar + Orange + endenum + Foo.Apple = Bar.Orange + END + v9.CheckSourceFailure(lines, 'E1423: Enum value "Foo.Apple" cannot be modified', 8) + + # Try assigning one enum item to another enum item in a function + lines =<< trim END + vim9script + enum Foo + Apple + endenum + enum Bar + Orange + endenum + def Fn() + Foo.Apple = Bar.Orange + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1423: Enum value "Foo.Apple" cannot be modified', 1) +enddef + +" Test for using enum in an expression +def Test_enum_expr() + var lines =<< trim END + vim9script + enum Color + Red, Blue, Green + endenum + var a: number = 1 + Color + END + v9.CheckSourceFailure(lines, 'E1421: Enum "Color" cannot be used as a value', 5) + + lines =<< trim END + vim9script + enum Color + Red, Blue, Green + endenum + var a: number = 1 + Color.Red + END + v9.CheckSourceFailure(lines, 'E1424: Using an Enum "Color" as a Number', 5) + + lines =<< trim END + vim9script + enum Color + Red, Blue, Green + endenum + var s: string = "abc" .. Color + END + v9.CheckSourceFailure(lines, 'E1421: Enum "Color" cannot be used as a value', 5) + + lines =<< trim END + vim9script + enum Color + Red, Blue, Green + endenum + var s: string = "abc" .. Color.Red + END + v9.CheckSourceFailure(lines, 'E1425: Using an Enum "Color" as a String', 5) +enddef + +" Using an enum in a lambda function +def Test_enum_lambda() + var lines =<< trim END + vim9script + enum Planet + Mercury, + Venus, + Earth, + endenum + var Fn = (p: Planet): Planet => p + for [idx, v] in items([Planet.Mercury, Planet.Venus, Planet.Earth]) + assert_equal(idx, Fn(v).ordinal) + endfor + END + v9.CheckSourceSuccess(lines) +enddef + +" Comparison using enums +def Test_enum_compare() + var lines =<< trim END + vim9script + enum Planet + Mercury, + Venus, + Earth, + endenum + enum Fruit + Apple, + Orange + endenum + + var p: Planet = Planet.Venus + var f: Fruit = Fruit.Orange + assert_equal(true, p == Planet.Venus) + assert_equal(false, p == Planet.Earth) + assert_equal(false, p == f) + assert_equal(true, Planet.Mercury == Planet.Mercury) + assert_equal(true, Planet.Venus != Planet.Earth) + assert_equal(true, Planet.Mercury != Fruit.Apple) + + def Fn1() + var p2: Planet = Planet.Venus + var f2: Fruit = Fruit.Orange + assert_equal(true, p2 == Planet.Venus) + assert_equal(false, p2 == Planet.Earth) + assert_equal(false, p2 == f2) + enddef + Fn1() + + # comparison using "is" and "isnot" + assert_equal(true, p is Planet.Venus) + assert_equal(true, p isnot Planet.Earth) + assert_equal(false, p is Fruit.Orange) + assert_equal(true, p isnot Fruit.Orange) + def Fn2(arg: Planet) + assert_equal(true, arg is Planet.Venus) + assert_equal(true, arg isnot Planet.Earth) + assert_equal(false, arg is Fruit.Orange) + assert_equal(true, arg isnot Fruit.Orange) + enddef + Fn2(p) + + class A + endclass + var o: A = A.new() + assert_equal(false, p == o) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using an enum as a default argument to a function +def Test_enum_default_arg() + var lines =<< trim END + vim9script + enum Day + Monday, Tuesday, Wednesday + endenum + def Fn(d: Day = Day.Tuesday): Day + return d + enddef + assert_equal(Day.Tuesday, Fn()) + assert_equal(Day.Wednesday, Fn(Day.Wednesday)) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for enum garbage collection +func Test_enum_garbagecollect() + let lines =<< trim END + vim9script + enum Car + Honda, Ford, Tesla + endenum + assert_equal(1, Car.Ford.ordinal) + call test_garbagecollect_now() + assert_equal(1, Car.Ford.ordinal) + var c: Car = Car.Tesla + assert_equal(2, c.ordinal) + call test_garbagecollect_now() + assert_equal(2, c.ordinal) + END + call v9.CheckSourceSuccess(lines) + + " garbage collection with a variable of type any + let lines =<< trim END + vim9script + enum Car + Honda, Ford, Tesla + endenum + call test_garbagecollect_now() + var c: any = Car.Tesla + call test_garbagecollect_now() + assert_equal(Car.Tesla, c) + END + call v9.CheckSourceSuccess(lines) + + " garbage collection with function arguments and return types + let lines =<< trim END + vim9script + enum Car + Honda, Ford, Tesla + endenum + def Fn(a: Car): Car + assert_equal(Car.Ford, a) + return Car.Tesla + enddef + call test_garbagecollect_now() + var b: Car = Car.Ford + call test_garbagecollect_now() + assert_equal(Car.Tesla, Fn(b)) + call test_garbagecollect_now() + END + call v9.CheckSourceSuccess(lines) +endfunc + +" Test for the enum values class variable +def Test_enum_values() + var lines =<< trim END + vim9script + enum Car + Honda, Ford, Tesla + endenum + var l: list = Car.values + assert_equal(Car.Ford, l[1]) + END + v9.CheckSourceSuccess(lines) + + # empty enum + lines =<< trim END + vim9script + enum Car + endenum + assert_equal([], Car.values) + END + v9.CheckSourceSuccess(lines) + + # single value + lines =<< trim END + vim9script + enum Car + Honda + endenum + assert_equal([Car.Honda], Car.values) + END + v9.CheckSourceSuccess(lines) + + lines =<< trim END + vim9script + enum A + Red, + Blue + static def GetValues(): list + return values + enddef + endenum + assert_equal([A.Red, A.Blue], A.GetValues()) + END + v9.CheckSourceSuccess(lines) + + # Other class variables in an enum should not be added to 'values' + lines =<< trim END + vim9script + enum LogLevel + Error, Warn + static const x: number = 22 + endenum + assert_equal([LogLevel.Error, LogLevel.Warn], LogLevel.values) + END + v9.CheckSourceSuccess(lines) + + # Other class variable of enum type should not be added to 'values' + lines =<< trim END + vim9script + enum LogLevel + Error, Warn + static const x: LogLevel = LogLevel.Warn + endenum + assert_equal([LogLevel.Error, LogLevel.Warn], LogLevel.values) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test comments in enums +def Test_enum_comments() + var lines =<< trim END + vim9script + enum Car # cars + # before enum + Honda, # honda + # before enum + Ford # ford + endenum + assert_equal(1, Car.Ford.ordinal) + END + v9.CheckSourceSuccess(lines) + + # Test for using an unsupported comment + lines =<< trim END + vim9script + enum Car + Honda, + Ford, + #{ + endenum + defcompile + END + v9.CheckSourceFailure(lines, 'E1170: Cannot use #{ to start a comment', 4) +enddef + +" Test string() with enums +def Test_enum_string() + var lines =<< trim END + vim9script + enum Car + Honda, + Ford + endenum + assert_equal("enum Car", string(Car)) + assert_equal("Car.Honda", string(Car.Honda)) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for importing an enum +def Test_enum_import() + var lines =<< trim END + vim9script + export enum Star + Gemini, + Orion, + Pisces + endenum + END + writefile(lines, 'Xenumexport.vim', 'D') + + lines =<< trim END + vim9script + import './Xenumexport.vim' as mod + + var s1: mod.Star = mod.Star.Orion + assert_equal(true, s1 == mod.Star.Orion) + assert_equal(2, mod.Star.Pisces.ordinal) + var l1: list = mod.Star.values + assert_equal("Star.Orion", string(l1[1])) + assert_equal(s1, l1[1]) + + def Fn() + var s2: mod.Star = mod.Star.Orion + assert_equal(true, s2 == mod.Star.Orion) + assert_equal(2, mod.Star.Pisces.ordinal) + var l2: list = mod.Star.values + assert_equal("Star.Orion", string(l2[1])) + assert_equal(s2, l2[1]) + enddef + Fn() + END + v9.CheckScriptSuccess(lines) +enddef + +" Test for using test_refcount() with enum +def Test_enum_refcount() + var lines =<< trim END + vim9script + enum Foo + endenum + assert_equal(1, test_refcount(Foo)) + + enum Star + Gemini, + Orion, + endenum + assert_equal(3, test_refcount(Star)) + assert_equal(2, test_refcount(Star.Gemini)) + assert_equal(2, test_refcount(Star.Orion)) + + var s: Star + assert_equal(3, test_refcount(Star)) + assert_equal(-1, test_refcount(s)) + s = Star.Orion + assert_equal(3, test_refcount(Star)) + assert_equal(3, test_refcount(s)) + assert_equal(2, test_refcount(Star.Gemini)) + var t = s + assert_equal(3, test_refcount(Star)) + assert_equal(4, test_refcount(s)) + assert_equal(4, test_refcount(Star.Orion)) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for defining an enum with additional object variables and methods +def Test_enum_enhanced() + var lines =<< trim END + vim9script + enum Vehicle + car(4, 5, 400), + bus(6, 50, 800), + bicycle(2, 1, 0) + + final tires: number + final passengers: number + final carbonPerKilometer: number + + def new(t: number, p: number, cpk: number) + this.tires = t + this.passengers = p + this.carbonPerKilometer = cpk + enddef + + def CarbonFootprint(): float + return round(this.carbonPerKilometer / this.passengers) + enddef + + def IsTwoWheeled(): bool + return this == Vehicle.bicycle + enddef + + def CompareTo(other: Vehicle): float + return this.CarbonFootprint() - other.CarbonFootprint() + enddef + endenum + + var v: Vehicle = Vehicle.bus + assert_equal([6, 50, 800], [v.tires, v.passengers, v.carbonPerKilometer]) + assert_equal(true, Vehicle.bicycle.IsTwoWheeled()) + assert_equal(false, Vehicle.car.IsTwoWheeled()) + assert_equal(16.0, Vehicle.bus.CarbonFootprint()) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for the enum value 'name' variable +def Test_enum_name() + # Check the names of enum values + var lines =<< trim END + vim9script + enum Planet + Mercury, + Venus, + Earth + endenum + assert_equal('Mercury', Planet.Mercury.name) + assert_equal('Venus', Planet.Venus.name) + assert_equal('Earth', Planet.Earth.name) + assert_equal('string', typename(Planet.Earth.name)) + END + v9.CheckSourceSuccess(lines) + + # Check the name of enum items in the constructor + lines =<< trim END + vim9script + enum Planet + Mercury("Mercury"), + Venus("Venus"), + Earth("Earth") + + def new(s: string) + assert_equal(s, this.name) + enddef + endenum + defcompile + END + v9.CheckSourceSuccess(lines) + + # Try assigning to the name of an enum + lines =<< trim END + vim9script + enum Fruit + Apple + endenum + Fruit.Apple.name = 'foo' + END + v9.CheckSourceFailure(lines, 'E1335: Variable "name" in class "Fruit" is not writable', 5) + + # Try assigning to the name of an enum in a function + lines =<< trim END + vim9script + enum Fruit + Apple + endenum + def Fn() + Fruit.Apple.name = 'bar' + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1423: Enum value "Fruit.name" cannot be modified', 1) + + # Try to overwrite an enum value name in the enum constructor + lines =<< trim END + vim9script + enum Planet + Mercury, + Venus + + def new() + this.name = 'foo' + enddef + endenum + END + v9.CheckSourceFailure(lines, 'E1427: Enum "Planet" name cannot be modified', 1) + + # Try to declare an object variable named 'name' + lines =<< trim END + vim9script + enum Planet + Mercury + var name: string + endenum + END + v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: name', 4) +enddef + +" Test for the enum value 'ordinal' variable +def Test_enum_ordinal() + # Check the ordinal values of enum items + var lines =<< trim END + vim9script + enum Planet + Mercury, + Venus, + Earth + endenum + assert_equal(0, Planet.Mercury.ordinal) + assert_equal(1, Planet.Venus.ordinal) + assert_equal(2, Planet.Earth.ordinal) + assert_equal('number', typename(Planet.Earth.ordinal)) + END + v9.CheckSourceSuccess(lines) + + # Check the ordinal value of enum items in the constructor + lines =<< trim END + vim9script + enum Planet + Mercury(0), + Venus(1), + Earth(2) + + def new(v: number) + assert_equal(v, this.ordinal) + enddef + endenum + defcompile + END + v9.CheckSourceSuccess(lines) + + # Try assigning to the ordinal value of an enum + lines =<< trim END + vim9script + enum Fruit + Apple + endenum + Fruit.Apple.ordinal = 20 + END + v9.CheckSourceFailure(lines, 'E1335: Variable "ordinal" in class "Fruit" is not writable', 5) + + # Try assigning to the ordinal value of an enum in a function + lines =<< trim END + vim9script + enum Fruit + Apple + endenum + def Fn() + Fruit.Apple.ordinal = 20 + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1423: Enum value "Fruit.ordinal" cannot be modified', 1) + + # Try to overwrite an enum value ordinal in the enum constructor + lines =<< trim END + vim9script + enum Planet + Mercury, + Venus + + def new() + this.ordinal = 20 + enddef + endenum + END + v9.CheckSourceFailure(lines, 'E1426: Enum "Planet" ordinal value cannot be modified', 1) + + # Try to declare an object variable named 'ordinal' + lines =<< trim END + vim9script + enum Planet + Mercury + var ordinal: number + endenum + END + v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: ordinal', 4) +enddef + +" Test for trying to create a new enum object using the constructor +def Test_enum_invoke_constructor() + var lines =<< trim END + vim9script + enum Foo + endenum + var f: Foo = Foo.new() + END + v9.CheckSourceFailure(lines, 'E1325: Method "new" not found in class "Foo"', 4) + + lines =<< trim END + vim9script + enum Fruit + Apple, + Orange + endenum + var f: Fruit = Fruit.new() + END + v9.CheckSourceFailure(lines, 'E1325: Method "new" not found in class "Fruit"', 6) + + lines =<< trim END + vim9script + enum Fruit + Apple, + Orange + def newFruit() + enddef + endenum + var f: Fruit = Fruit.newFruit() + END + v9.CheckSourceFailure(lines, 'E1325: Method "newFruit" not found in class "Fruit"', 8) + + lines =<< trim END + vim9script + enum Fruit + Apple, + Orange + endenum + def Fn() + var f: Fruit = Fruit.new() + enddef + Fn() + END + v9.CheckSourceFailure(lines, 'E1325: Method "new" not found in class "Fruit"', 1) + + # error in the enum constructor + lines =<< trim END + vim9script + enum Planet + earth + def new() + x = 123 + enddef + endenum + END + v9.CheckSourceFailureList(lines, ['E1100:', 'E1100:'], 1) +enddef + +" Test for checking "this" in an enum constructor +def Test_enum_this_in_constructor() + var lines =<< trim END + vim9script + enum A + Red("A.Red"), + Blue("A.Blue"), + Green("A.Green") + + def new(s: string) + assert_equal(s, string(this)) + enddef + endenum + defcompile + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using member variables in an enum object +def Test_enum_object_variable() + var lines =<< trim END + vim9script + enum Planet + Jupiter(95), + Saturn(146) + + var moons: number + endenum + assert_equal(95, Planet.Jupiter.moons) + assert_equal(146, Planet.Saturn.moons) + END + v9.CheckSourceSuccess(lines) + + # Use a final object variable + lines =<< trim END + vim9script + enum Planet + Jupiter(95), + Saturn(146) + + final moons: number + def new(n: number) + this.moons = n + enddef + endenum + assert_equal(95, Planet.Jupiter.moons) + assert_equal(146, Planet.Saturn.moons) + END + v9.CheckSourceSuccess(lines) + + # Use a const object variable + lines =<< trim END + vim9script + enum Planet + Mars(false), + Jupiter(true) + + const has_ring: bool + def new(r: bool) + this.has_ring = r + enddef + endenum + assert_equal(false, Planet.Mars.has_ring) + assert_equal(true, Planet.Jupiter.has_ring) + END + v9.CheckSourceSuccess(lines) + + # Use a regular object variable + lines =<< trim END + vim9script + enum Fruit + Apple, + Orange + + final farm: string = 'SunValley' + endenum + assert_equal('SunValley', Fruit.Apple.farm) + assert_equal('SunValley', Fruit.Apple.farm) + END + v9.CheckSourceSuccess(lines) + + # Invoke the default constructor with an object variable + lines =<< trim END + vim9script + enum Fruit + Apple('foo'), + Orange('bar') + + final t: string + endenum + assert_equal('foo', Fruit.Apple.t) + assert_equal('bar', Fruit.Orange.t) + END + v9.CheckSourceSuccess(lines) + + # Invoke the default constructor with an argument but without the object + # variable + lines =<< trim END + vim9script + enum Fruit + Apple, + Orange('bar') + endenum + defcompile + END + v9.CheckSourceFailure(lines, 'E118: Too many arguments for function: new', 5) + + # Define a default constructor with an argument, but don't pass it in when + # defining the enum value + lines =<< trim END + vim9script + enum Fruit + Apple(5), + Orange + + def new(t: number) + enddef + endenum + defcompile + END + v9.CheckSourceFailure(lines, 'E119: Not enough arguments for function: new', 8) +enddef + +" Test for using a custom constructor with an enum +def Test_enum_custom_constructor() + # space before "(" + var lines =<< trim END + vim9script + enum Fruit + Apple(10), + Orange (20) + + def new(t: number) + enddef + endenum + defcompile + END + v9.CheckSourceFailure(lines, "E1068: No white space allowed before '(': Orange (20)", 4) + + # no closing ")" + lines =<< trim END + vim9script + enum Fruit + Apple(10), + Orange (20 + + def new(t: number) + enddef + endenum + defcompile + END + v9.CheckSourceFailure(lines, "E1068: No white space allowed before '(': Orange (20", 4) + + # Specify constructor arguments split across multiple lines + lines =<< trim END + vim9script + enum Fruit + Apple(10, + 'foo'), Orange(20, + 'bar'), + Pear(30, + 'baz'), Mango(40, + 'qux') + + final n: number + final s: string + def new(t: number, str: string) + this.n = t + this.s = str + enddef + endenum + defcompile + assert_equal([10, 'foo'], [Fruit.Apple.n, Fruit.Apple.s]) + assert_equal([20, 'bar'], [Fruit.Orange.n, Fruit.Orange.s]) + assert_equal([30, 'baz'], [Fruit.Pear.n, Fruit.Pear.s]) + assert_equal([40, 'qux'], [Fruit.Mango.n, Fruit.Mango.s]) + END + v9.CheckSourceSuccess(lines) + + # specify multiple enums with constructor arguments in a single line + lines =<< trim END + vim9script + enum Fruit + Apple(10, 'foo'), Orange(20, 'bar'), Pear(30, 'baz'), Mango(40, 'qux') + const n: number + const s: string + endenum + defcompile + assert_equal([10, 'foo'], [Fruit.Apple.n, Fruit.Apple.s]) + assert_equal([20, 'bar'], [Fruit.Orange.n, Fruit.Orange.s]) + assert_equal([30, 'baz'], [Fruit.Pear.n, Fruit.Pear.s]) + assert_equal([40, 'qux'], [Fruit.Mango.n, Fruit.Mango.s]) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for using class variables in an enum class +def Test_enum_class_variable() + var lines =<< trim END + vim9script + enum Fruit + Apple, + Orange + + static var farm: string = 'SunValley' + endenum + assert_equal('SunValley', Fruit.farm) + END + v9.CheckSourceSuccess(lines) +enddef + +" Test for converting an enum value to a string and then back to an enum value +def Test_enum_eval() + var lines =<< trim END + vim9script + enum Color + Red, + Blue + endenum + var s: string = string(Color.Blue) + var e = eval(s) + assert_equal(Color.Blue, e) + assert_equal(1, e.ordinal) + END + v9.CheckSourceSuccess(lines) +enddef + +" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testing.c b/src/testing.c --- a/src/testing.c +++ b/src/testing.c @@ -1091,9 +1091,8 @@ f_test_refcount(typval_T *argvars, typva case VAR_SPECIAL: case VAR_STRING: case VAR_INSTR: - case VAR_CLASS: - case VAR_OBJECT: break; + case VAR_JOB: #ifdef FEAT_JOB_CHANNEL if (argvars[0].vval.v_job != NULL) @@ -1132,6 +1131,14 @@ f_test_refcount(typval_T *argvars, typva if (argvars[0].vval.v_dict != NULL) retval = argvars[0].vval.v_dict->dv_refcount - 1; break; + case VAR_CLASS: + if (argvars[0].vval.v_class != NULL) + retval = argvars[0].vval.v_class->class_refcount - 1; + break; + case VAR_OBJECT: + if (argvars[0].vval.v_object != NULL) + retval = argvars[0].vval.v_object->obj_refcount - 1; + break; case VAR_TYPEALIAS: if (argvars[0].vval.v_typealias != NULL) retval = argvars[0].vval.v_typealias->ta_refcount - 1; diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -266,7 +266,13 @@ tv_get_bool_or_number_chk( check_typval_is_value(varp); break; case VAR_OBJECT: - emsg(_(e_using_object_as_number)); + { + class_T *cl = varp->vval.v_object->obj_class; + if (cl != NULL && IS_ENUM(cl)) + semsg(_(e_using_enum_str_as_number), cl->class_name); + else + emsg(_(e_using_object_as_number)); + } break; case VAR_VOID: emsg(_(e_cannot_use_void_value)); @@ -1139,7 +1145,13 @@ tv_get_string_buf_chk_strict(typval_T *v check_typval_is_value(varp); break; case VAR_OBJECT: - emsg(_(e_using_object_as_string)); + { + class_T *cl = varp->vval.v_object->obj_class; + if (cl != NULL && IS_ENUM(cl)) + semsg(_(e_using_enum_str_as_string), cl->class_name); + else + emsg(_(e_using_object_as_string)); + } break; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL 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 */ /**/ + 219, +/**/ 218, /**/ 217, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -2164,7 +2164,9 @@ typedef int sock_T; #define VV_MAXCOL 105 #define VV_PYTHON3_VERSION 106 #define VV_TYPE_TYPEALIAS 107 -#define VV_LEN 108 // number of v: vars +#define VV_TYPE_ENUM 108 +#define VV_TYPE_ENUMVALUE 109 +#define VV_LEN 110 // number of v: vars // used for v_number in VAR_BOOL and VAR_SPECIAL #define VVAL_FALSE 0L // VAR_BOOL @@ -2188,6 +2190,8 @@ typedef int sock_T; #define VAR_TYPE_CLASS 12 #define VAR_TYPE_OBJECT 13 #define VAR_TYPE_TYPEALIAS 14 +#define VAR_TYPE_ENUM 15 +#define VAR_TYPE_ENUMVALUE 16 #define DICT_MAXNEST 100 // maximum nesting of lists and dicts diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -168,7 +168,8 @@ struct oc_newmember_S /* * Add a member to an object or a class. * Returns OK when successful, "init_expr" will be consumed then. - * Returns FAIL otherwise, caller might need to free "init_expr". + * Returns OK on success and FAIL on memory allocation failure (caller might + * need to free "init_expr"). */ static int add_member( @@ -323,13 +324,15 @@ validate_extends_class( } if (tv.v_type != VAR_CLASS || tv.vval.v_class == NULL - || (is_class - && (tv.vval.v_class->class_flags & CLASS_INTERFACE) != 0) - || (!is_class - && (tv.vval.v_class->class_flags & CLASS_INTERFACE) == 0)) - // a interface cannot extend a class and a class cannot extend an - // interface. + || (is_class && IS_INTERFACE(tv.vval.v_class)) + || (!is_class && !IS_INTERFACE(tv.vval.v_class)) + || (is_class && IS_ENUM(tv.vval.v_class))) + { + // a class cannot extend an interface + // an interface cannot extend a class + // a class cannot extend an enum. semsg(_(e_cannot_extend_str), extends_name); + } else { class_T *extends_cl = tv.vval.v_class; @@ -793,7 +796,7 @@ validate_implements_classes( if (tv.v_type != VAR_CLASS || tv.vval.v_class == NULL - || (tv.vval.v_class->class_flags & CLASS_INTERFACE) == 0) + || !IS_INTERFACE(tv.vval.v_class)) { semsg(_(e_not_valid_interface_str), impl); success = FALSE; @@ -1234,14 +1237,14 @@ add_lookup_tables(class_T *cl, class_T * * Add class members to a new class. Allocate a typval for each class member * and initialize it. */ - static void + static int add_class_members(class_T *cl, exarg_T *eap, garray_T *type_list_gap) { // Allocate a typval for each class member and initialize it. cl->class_members_tv = ALLOC_CLEAR_MULT(typval_T, cl->class_class_member_count); if (cl->class_members_tv == NULL) - return; + return FAIL; for (int i = 0; i < cl->class_class_member_count; ++i) { @@ -1250,19 +1253,19 @@ add_class_members(class_T *cl, exarg_T * if (m->ocm_init != NULL) { typval_T *etv = eval_expr(m->ocm_init, eap); - if (etv != NULL) - { - if (m->ocm_type->tt_type == VAR_ANY - && !(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. - m->ocm_type = typval2type(etv, get_copyID(), - type_list_gap, - TVTT_DO_MEMBER|TVTT_MORE_SPECIFIC); - *tv = *etv; - vim_free(etv); - } + if (etv == NULL) + return FAIL; + + if (m->ocm_type->tt_type == VAR_ANY + && !(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. + m->ocm_type = typval2type(etv, get_copyID(), + type_list_gap, + TVTT_DO_MEMBER|TVTT_MORE_SPECIFIC); + *tv = *etv; + vim_free(etv); } else { @@ -1273,6 +1276,8 @@ add_class_members(class_T *cl, exarg_T * if (m->ocm_flags & OCMFLAG_CONST) item_lock(tv, DICT_MAXNEST, TRUE, TRUE); } + + return OK; } /* @@ -1284,13 +1289,21 @@ add_default_constructor( garray_T *classfunctions_gap, garray_T *type_list_gap) { - garray_T fga; + garray_T fga; + int is_enum = IS_ENUM(cl); ga_init2(&fga, 1, 1000); ga_concat(&fga, (char_u *)"new("); for (int i = 0; i < cl->class_obj_member_count; ++i) { - if (i > 0) + if (i < 2 && is_enum) + // The first two object variables in an enum are the enum value + // name and ordinal. Don't initialize these object variables in + // the default constructor as they are already initialized right + // after creating the object. + continue; + + if (i > (is_enum ? 2 : 0)) ga_concat(&fga, (char_u *)", "); ga_concat(&fga, (char_u *)"this."); ocmember_T *m = cl->class_obj_members + i; @@ -1336,6 +1349,7 @@ add_default_constructor( * Add the class methods and object methods to the new class "cl". * When extending a class "extends_cl", add the instance methods from the * parent class also. + * Returns OK on success and FAIL on memory allocation failure. */ static int add_classfuncs_objmethods( @@ -1373,7 +1387,7 @@ add_classfuncs_objmethods( if (gap->ga_len != 0) mch_memmove(*fup, gap->ga_data, sizeof(ufunc_T *) * gap->ga_len); - vim_free(gap->ga_data); + VIM_CLEAR(gap->ga_data); if (loop == 1) cl->class_class_function_count_child = gap->ga_len; else @@ -1422,6 +1436,8 @@ add_classfuncs_objmethods( if (loop == 2) fp->uf_flags |= FC_OBJECT; } + + ga_clear(gap); } return OK; @@ -1471,6 +1487,246 @@ find_class_name_end(char_u *arg) return end; } +/* + * Returns TRUE if the enum value "varname" is already defined. + */ + static int +is_duplicate_enum( + garray_T *enum_gap, + char_u *varname, + char_u *varname_end) +{ + char_u *name = vim_strnsave(varname, varname_end - varname); + int dup = FALSE; + + for (int i = 0; i < enum_gap->ga_len; ++i) + { + ocmember_T *m = ((ocmember_T *)enum_gap->ga_data) + i; + if (STRCMP(name, m->ocm_name) == 0) + { + semsg(_(e_duplicate_enum_str), name); + dup = TRUE; + break; + } + } + + vim_free(name); + return dup; +} + +/* + * Parse the enum values in "line" separated by comma and add them to "gap". + * If the last enum value is found, then "enum_end" is set to TRUE. + */ + static int +enum_parse_values( + exarg_T *eap, + class_T *en, + char_u *line, + garray_T *gap, + int *num_enum_values, + int *enum_end) +{ + evalarg_T evalarg; + char_u *p = line; + char initexpr_buf[1024]; + char_u last_char = NUL; + int rc = OK; + + fill_evalarg_from_eap(&evalarg, eap, FALSE); + + int did_emsg_before = did_emsg; + while (*p != NUL) + { + // ignore comment + if (*p == '#') + break; + + if (!eval_isnamec1(*p)) + { + semsg(_(e_invalid_enum_value_declaration_str), p); + break; + } + + char_u *eni_name_start = p; + char_u *eni_name_end = to_name_end(p, FALSE); + + if (is_duplicate_enum(gap, eni_name_start, eni_name_end)) + break; + + p = skipwhite(eni_name_end); + + char_u *init_expr = NULL; + if (*p == '(') + { + if (VIM_ISWHITE(p[-1])) + { + semsg(_(e_no_white_space_allowed_before_str_str), "(", line); + break; + } + + char_u *expr_start, *expr_end; + + p = eni_name_start; + (void)skip_expr_concatenate(&p, &expr_start, &expr_end, &evalarg); + + while (*expr_start && *expr_start != '(') + expr_start++; + + if (expr_end > expr_start) + init_expr = vim_strnsave(expr_start, expr_end - expr_start); + } + + if (init_expr == NULL) + vim_snprintf(initexpr_buf, sizeof(initexpr_buf), "%s.new()", + en->class_name); + else + { + vim_snprintf(initexpr_buf, sizeof(initexpr_buf), "%s.new%s", + en->class_name, init_expr); + vim_free(init_expr); + } + if (add_member(gap, eni_name_start, eni_name_end, FALSE, + TRUE, TRUE, TRUE, &en->class_object_type, + vim_strsave((char_u *)initexpr_buf)) == FAIL) + break; + + ++*num_enum_values; + + if (*p != '#') + last_char = *p; + + if (*p != NUL && *p != ',') + break; + + if (*p == ',') + { + if (!IS_WHITE_OR_NUL(p[1])) + { + semsg(_(e_white_space_required_after_str_str), ",", line); + break; + } + if (VIM_ISWHITE(p[-1])) + { + semsg(_(e_no_white_space_allowed_before_str_str), ",", line); + break; + } + p = skipwhite(p + 1); + } + } + + if (*p != NUL && *p != '#') + { + if (did_emsg == did_emsg_before) + semsg(_(e_missing_comma_before_argument_str), p); + rc = FAIL; + } + + if (last_char != ',') + // last enum value should not be terminated by "," + *enum_end = TRUE; + + // Free the memory pointed by expr_start. + clear_evalarg(&evalarg, NULL); + + return rc; +} + +/* + * Add the "values" class variable (List of enum value objects) to the enum + * class "en" + */ + static int +enum_add_values_member( + class_T *en, + garray_T *gap, + int num_enum_values, + garray_T *type_list_gap) +{ + garray_T fga; + int rc = FAIL; + + ga_init2(&fga, 1, 1000); + ga_concat(&fga, (char_u *)"["); + for (int i = 0; i < num_enum_values; ++i) + { + ocmember_T *m = ((ocmember_T *)gap->ga_data) + i; + + if (i > 0) + ga_concat(&fga, (char_u *)", "); + ga_concat(&fga, en->class_name); + ga_concat(&fga, (char_u *)"."); + ga_concat(&fga, (char_u *)m->ocm_name); + } + ga_concat(&fga, (char_u *)"]"); + ga_append(&fga, NUL); + + char_u *varname = (char_u *)"values"; + + type_T *type = get_type_ptr(type_list_gap); + if (type == NULL) + goto done; + + type->tt_type = VAR_LIST; + type->tt_member = get_type_ptr(type_list_gap); + if (type->tt_member != NULL) + { + type->tt_member->tt_type = VAR_OBJECT; + type->tt_member->tt_class = en; + } + + rc = add_member(gap, varname, varname + 6, FALSE, FALSE, TRUE, TRUE, type, + vim_strsave((char_u *)fga.ga_data)); + +done: + vim_free(fga.ga_data); + + return rc; +} + +/* + * Clear the constructor method names in a enum class, so that an enum class + * cannot be instantiated. + */ + static void +enum_clear_constructors(class_T *en) +{ + for (int i = 0; i < en->class_class_function_count; ++i) + { + ufunc_T *fp = en->class_class_functions[i]; + + if (fp->uf_flags & FC_NEW) + *fp->uf_name = NUL; + } +} + +/* + * Initialize the name and ordinal object variable in the enum value "enval" in + * the enum "en". These values are set during the enum value object creation. + */ + void +enum_set_internal_obj_vars(class_T *en, object_T *enval) +{ + int i; + + for (i = 0; i < en->class_class_member_count; ++i) + { + typval_T *en_tv = en->class_members_tv + i; + if (en_tv != NULL && en_tv->v_type == VAR_UNKNOWN) + break; + } + + // First object variable is the name + ocmember_T *value_ocm = en->class_class_members + i; + typval_T *name_tv = (typval_T *)(enval + 1); + name_tv->v_type = VAR_STRING; + name_tv->vval.v_string = vim_strsave(value_ocm->ocm_name); + + // Second object variable is the ordinal + typval_T *ord_tv = (typval_T *)(name_tv + 1); + ord_tv->v_type = VAR_NUMBER; + ord_tv->vval.v_number = i; +} /* * Handle ":class" and ":abstract class" up to ":endclass". @@ -1479,10 +1735,12 @@ find_class_name_end(char_u *arg) void ex_class(exarg_T *eap) { - int is_class = eap->cmdidx == CMD_class; // FALSE for :interface + int is_class = eap->cmdidx == CMD_class; + int is_abstract = eap->cmdidx == CMD_abstract; + int is_enum = eap->cmdidx == CMD_enum; + int is_interface; long start_lnum = SOURCING_LNUM; char_u *arg = eap->arg; - int is_abstract = eap->cmdidx == CMD_abstract; if (is_abstract) { @@ -1495,12 +1753,16 @@ ex_class(exarg_T *eap) is_class = TRUE; } + is_interface = !is_class && !is_enum; + if (!current_script_is_vim9() || (cmdmod.cmod_flags & CMOD_LEGACY) || !getline_equal(eap->ea_getline, eap->cookie, getsourceline)) { if (is_class) emsg(_(e_class_can_only_be_defined_in_vim9_script)); + else if (is_enum) + emsg(_(e_enum_can_only_be_defined_in_vim9_script)); else emsg(_(e_interface_can_only_be_defined_in_vim9_script)); return; @@ -1510,6 +1772,8 @@ ex_class(exarg_T *eap) { if (is_class) semsg(_(e_class_name_must_start_with_uppercase_letter_str), arg); + else if (is_enum) + semsg(_(e_enum_name_must_start_with_uppercase_letter_str), arg); else semsg(_(e_interface_name_must_start_with_uppercase_letter_str), arg); @@ -1523,11 +1787,6 @@ ex_class(exarg_T *eap) } char_u *name_start = arg; - // "export class" gets used when creating the class, don't use "is_export" - // for the items inside the class. - int class_export = is_export; - is_export = FALSE; - // TODO: // generics: @@ -1545,6 +1804,11 @@ ex_class(exarg_T *eap) // specifies SomeInterface if (STRNCMP(arg, "extends", 7) == 0 && IS_WHITE_OR_NUL(arg[7])) { + if (is_enum) + { + emsg(_(e_enum_cannot_extend_class)); + goto early_ret; + } if (extends != NULL) { emsg(_(e_duplicate_extends)); @@ -1567,7 +1831,7 @@ ex_class(exarg_T *eap) else if (STRNCMP(arg, "implements", 10) == 0 && IS_WHITE_OR_NUL(arg[10])) { - if (!is_class) + if (is_interface) { emsg(_(e_interface_cannot_use_implements)); goto early_ret; @@ -1652,11 +1916,15 @@ early_ret: class_T *cl = NULL; class_T *extends_cl = NULL; // class from "extends" argument class_T **intf_classes = NULL; + int num_enum_values = 0; cl = ALLOC_CLEAR_ONE(class_T); if (cl == NULL) goto cleanup; - if (!is_class) + + if (is_enum) + cl->class_flags = CLASS_ENUM; + else if (is_interface) cl->class_flags = CLASS_INTERFACE; else if (is_abstract) cl->class_flags = CLASS_ABSTRACT; @@ -1666,22 +1934,48 @@ early_ret: if (cl->class_name == NULL) goto cleanup; + cl->class_type.tt_type = VAR_CLASS; + cl->class_type.tt_class = cl; + cl->class_object_type.tt_type = VAR_OBJECT; + cl->class_object_type.tt_class = cl; + // Add the class to the script-local variables. // TODO: handle other context, e.g. in a function // TODO: does uf_hash need to be cleared? typval_T tv; tv.v_type = VAR_CLASS; tv.vval.v_class = cl; - is_export = class_export; SOURCING_LNUM = start_lnum; int rc = set_var_const(cl->class_name, current_sctx.sc_sid, NULL, &tv, FALSE, 0, 0); if (rc == FAIL) goto cleanup; + if (is_enum) + { + // All the enum classes have the name and ordinal object variables. + char_u *varname = (char_u *)"name"; + if (add_member(&objmembers, varname, varname + 4, FALSE, FALSE, TRUE, + TRUE, &t_string, NULL) == FAIL) + goto cleanup; + + varname = (char_u *)"ordinal"; + if (add_member(&objmembers, varname, varname + 7, FALSE, FALSE, TRUE, + TRUE, &t_number, NULL) == FAIL) + goto cleanup; + } + + // "export class" gets used when creating the class, don't use "is_export" + // for the items inside the class. + is_export = FALSE; + + // When parsing an enum definition, this denotes whether all the enumerated + // values are parsed or not. + int enum_end = FALSE; + /* * Go over the body of the class/interface until "endclass" or - * "endinterface" is found. + * "endinterface" or "endenum" is found. */ char_u *theline = NULL; int success = FALSE; @@ -1704,10 +1998,32 @@ early_ret: } char_u *p = line; - char *end_name = is_class ? "endclass" : "endinterface"; - if (checkforcmd(&p, end_name, is_class ? 4 : 5)) + + char *end_name; + int shortlen; + int fullen; + if (is_class) + { + end_name = "endclass"; + shortlen = 4; + fullen = 8; + } + else if (is_enum) { - if (STRNCMP(line, end_name, is_class ? 8 : 12) != 0) + end_name = "endenum"; + shortlen = 4; + fullen = 7; + } + else + { + end_name = "endinterface"; + shortlen = 5; + fullen = 12; + } + + if (checkforcmd(&p, end_name, shortlen)) + { + if (STRNCMP(line, end_name, fullen) != 0) semsg(_(e_command_cannot_be_shortened_str), line); else if (*p == '|' || !ends_excmd2(line, p)) semsg(_(e_trailing_characters_str), p); @@ -1715,13 +2031,34 @@ early_ret: success = TRUE; break; } - char *wrong_name = is_class ? "endinterface" : "endclass"; - if (checkforcmd(&p, wrong_name, is_class ? 5 : 4)) + + int wrong_endname = FALSE; + if (is_class) + wrong_endname = checkforcmd(&p, "endinterface", 5) + || checkforcmd(&p, "endenum", 4); + else if (is_enum) + wrong_endname = checkforcmd(&p, "endclass", 4) + || checkforcmd(&p, "endinterface", 5); + else + wrong_endname = checkforcmd(&p, "endclass", 4) + || checkforcmd(&p, "endenum", 4); + if (wrong_endname) { semsg(_(e_invalid_command_str_expected_str), line, end_name); break; } + if (is_enum && !enum_end) + { + // In an enum, all the enumerated values are at the beginning + // separated by comma. The class and object variables/methods + // follow the values. + if (enum_parse_values(eap, cl, line, &classmembers, + &num_enum_values, &enum_end) == FAIL) + break; + continue; + } + int has_public = FALSE; if (checkforcmd(&p, "public", 3)) { @@ -1730,7 +2067,7 @@ early_ret: semsg(_(e_command_cannot_be_shortened_str), line); break; } - if (!is_class) + if (is_interface) { emsg(_(e_public_variable_not_supported_in_interface)); break; @@ -1756,7 +2093,14 @@ early_ret: break; } - if (!is_class) + if (is_enum) + { + // "abstract" not supported in an enum + emsg(_(e_abstract_cannot_be_used_in_enum)); + break; + } + + if (is_interface) { // "abstract" not supported in an interface emsg(_(e_abstract_cannot_be_used_in_interface)); @@ -1789,7 +2133,7 @@ early_ret: break; } - if (!is_class) + if (is_interface) { emsg(_(e_static_member_not_supported_in_interface)); break; @@ -1812,7 +2156,7 @@ early_ret: has_var = TRUE; else if (checkforcmd(&p, "final", 5)) { - if (!is_class) + if (is_interface) { emsg(_(e_final_variable_not_supported_in_interface)); break; @@ -1821,7 +2165,7 @@ early_ret: } else if (checkforcmd(&p, "const", 5)) { - if (!is_class) + if (is_interface) { emsg(_(e_const_variable_not_supported_in_interface)); break; @@ -1867,7 +2211,7 @@ early_ret: break; } - if (!is_class && *varname == '_') + if (is_interface && *varname == '_') { // private variables are not supported in an interface semsg(_(e_protected_variable_not_supported_in_interface), @@ -1877,7 +2221,7 @@ early_ret: if (parse_member(eap, line, varname, has_public, &varname_end, &has_type, &type_list, &type, - is_class ? &init_expr: NULL) == FAIL) + !is_interface ? &init_expr: NULL) == FAIL) break; if (is_reserved_varname(varname, varname_end) @@ -1930,7 +2274,7 @@ early_ret: break; } - if (!is_class && *p == '_') + if (is_interface && *p == '_') { // private methods are not supported in an interface semsg(_(e_protected_method_not_supported_in_interface), p); @@ -1953,10 +2297,10 @@ early_ret: ga_init2(&lines_to_free, sizeof(char_u *), 50); int class_flags; - if (is_class) + if (is_interface) + class_flags = CF_INTERFACE; + else class_flags = abstract_method ? CF_ABSTRACT_METHOD : CF_CLASS; - else - class_flags = CF_INTERFACE; ufunc_T *uf = define_function(&ea, NULL, &lines_to_free, class_flags, objmembers.ga_data, objmembers.ga_len); ga_clear_strings(&lines_to_free); @@ -2011,15 +2355,25 @@ early_ret: { if (is_class) semsg(_(e_not_valid_command_in_class_str), line); + else if (is_enum) + semsg(_(e_not_valid_command_in_enum_str), line); else semsg(_(e_not_valid_command_in_interface_str), line); break; } } + + if (theline == NULL && !success && is_enum) + emsg(_(e_missing_endenum)); + vim_free(theline); + if (success && is_enum) + // Add the enum "values" class variable. + enum_add_values_member(cl, &classmembers, num_enum_values, &type_list); + /* - * Check a few things before defining the class. + * Check a few things */ // Check the "extends" class is valid. @@ -2067,7 +2421,8 @@ early_ret: if (success) { - // "endclass" encountered without failures: Create the class. + // "endclass" or "endinterface" or "endenum" encountered without any + // failures if (extends_cl != NULL) { @@ -2114,10 +2469,6 @@ early_ret: goto cleanup; } - // Allocate a typval for each class member and initialize it. - if (is_class && cl->class_class_member_count > 0) - add_class_members(cl, eap, &type_list); - int have_new = FALSE; ufunc_T *class_func = NULL; for (int i = 0; i < classfunctions.ga_len; ++i) @@ -2133,7 +2484,7 @@ early_ret: if (have_new) // The return type of new() is an object of class "cl" class_func->uf_ret_type->tt_class = cl; - else if (is_class && !is_abstract && !have_new) + else if ((is_class || is_enum) && !is_abstract && !have_new) // No new() method was defined, add the default constructor. add_default_constructor(cl, &classfunctions, &type_list); @@ -2144,13 +2495,21 @@ early_ret: update_builtin_method_index(cl); - cl->class_type.tt_type = VAR_CLASS; - cl->class_type.tt_class = cl; - cl->class_object_type.tt_type = VAR_OBJECT; - cl->class_object_type.tt_class = cl; + class_created(cl); + + // Allocate a typval for each class member and initialize it. + if ((is_class || is_enum) && cl->class_class_member_count > 0) + if (add_class_members(cl, eap, &type_list) == FAIL) + goto cleanup; + cl->class_type_list = type_list; - class_created(cl); + if (is_enum) + { + // clear the constructor method names, so that an enum class cannot + // be instantiated + enum_clear_constructors(cl); + } // TODO: // - Fill hashtab with object members and methods ? @@ -2265,15 +2624,6 @@ oc_member_type_by_idx( } /* - * Handle ":enum" up to ":endenum". - */ - void -ex_enum(exarg_T *eap UNUSED) -{ - // TODO -} - -/* * Type aliases (:type) */ @@ -3334,8 +3684,14 @@ member_not_found_msg(class_T *cl, vartyp semsg(_(e_object_variable_str_accessible_only_using_object_str), varname, cl->class_name); else - semsg(_(e_class_variable_str_not_found_in_class_str), - varname, cl->class_name); + { + if (IS_ENUM(cl)) + semsg(_(e_enum_value_str_not_found_in_enum_str), + varname, cl->class_name); + else + semsg(_(e_class_variable_str_not_found_in_class_str), + varname, cl->class_name); + } } vim_free(varname); } @@ -3480,8 +3836,17 @@ object_string( garray_T ga; ga_init2(&ga, 1, 50); + class_T *cl = obj == NULL ? NULL : obj->obj_class; + if (cl != NULL && IS_ENUM(cl)) + { + ga_concat(&ga, cl->class_name); + char_u *name = ((typval_T *)(obj + 1))->vval.v_string; + ga_concat(&ga, (char_u *)"."); + ga_concat(&ga, name); + return ga.ga_data; + } + ga_concat(&ga, (char_u *)"object of "); - class_T *cl = obj == NULL ? NULL : obj->obj_class; ga_concat(&ga, cl == NULL ? (char_u *)"[unknown]" : cl->class_name); if (cl != NULL) diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1614,6 +1614,13 @@ lhs_class_member_modifiable(lhs_T *lhs, return FALSE; } + if (IS_ENUM(cl)) + { + semsg(_(e_enumvalue_str_cannot_be_modified), cl->class_name, + m->ocm_name); + return FALSE; + } + // If it is private member variable, then accessing it outside the // class is not allowed. // If it is a read only class variable, then it can be modified @@ -2037,6 +2044,25 @@ compile_lhs( return FAIL; } + if (IS_ENUM(cl)) + { + if (!inside_class(cctx, cl)) + { + semsg(_(e_enumvalue_str_cannot_be_modified), + cl->class_name, m->ocm_name); + return FALSE; + } + if (lhs->lhs_type->tt_type == VAR_OBJECT && + lhs->lhs_member_idx < 2) + { + char *msg = lhs->lhs_member_idx == 0 ? + e_enum_str_name_cannot_be_modified : + e_enum_str_ordinal_cannot_be_modified; + semsg(_(msg), cl->class_name); + return FALSE; + } + } + // If it is private member variable, then accessing it outside the // class is not allowed. // If it is a read only class variable, then it can be modified @@ -2304,7 +2330,7 @@ compile_load_lhs_with_index(lhs_T *lhs, if (compile_load_lhs(lhs, var_start, lhs->lhs_type, cctx) == FAIL) return FAIL; } - if (cl->class_flags & CLASS_INTERFACE) + if (IS_INTERFACE(cl)) return generate_GET_ITF_MEMBER(cctx, cl, lhs->lhs_member_idx, type); return generate_GET_OBJ_MEMBER(cctx, lhs->lhs_member_idx, type); } @@ -2453,7 +2479,7 @@ compile_assign_unlet( { class_T *cl = lhs->lhs_type->tt_class; - if (cl->class_flags & CLASS_INTERFACE) + if (IS_INTERFACE(cl)) { // "this.value": load "this" object and get the value // at index for an object or class member get the type @@ -3375,6 +3401,14 @@ compile_def_function( for (int i = 0; i < ufunc->uf_class->class_obj_member_count; ++i) { ocmember_T *m = &ufunc->uf_class->class_obj_members[i]; + + if (i < 2 && IS_ENUM(ufunc->uf_class)) + // The first two object variables in an enum are the name + // and the ordinal. These are set by the ISN_CONSTRUCT + // instruction. So don't generate instructions to set + // these variables. + continue; + if (m->ocm_init != NULL) { char_u *expr = m->ocm_init; @@ -3481,8 +3515,7 @@ compile_def_function( // Compiling a function in an interface is done to get the function type. // No code is actually compiled. - if (ufunc->uf_class != NULL - && (ufunc->uf_class->class_flags & CLASS_INTERFACE)) + if (ufunc->uf_class != NULL && IS_INTERFACE(ufunc->uf_class)) { ufunc->uf_def_status = UF_NOT_COMPILED; ret = OK; diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -3258,6 +3258,12 @@ exec_instructions(ectx_T *ectx) ++tv->vval.v_object->obj_class->class_refcount; tv->vval.v_object->obj_refcount = 1; object_created(tv->vval.v_object); + + // When creating an enum value object, initialize the name and + // ordinal object variables. + class_T *en = tv->vval.v_object->obj_class; + if (IS_ENUM(en)) + enum_set_internal_obj_vars(en, tv->vval.v_object); break; // execute Ex command line diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -446,7 +446,7 @@ compile_class_object_index(cctx_T *cctx, if (m_idx >= 0) { ufunc_T *fp = cl->class_obj_methods[m_idx]; - // Private methods are not accessible outside the class + // Private object methods are not accessible outside the class if (*name == '_' && !inside_class(cctx, cl)) { semsg(_(e_cannot_access_protected_method_str), fp->uf_name); @@ -488,7 +488,7 @@ compile_class_object_index(cctx_T *cctx, if (m_idx >= 0) { ufunc_T *fp = cl->class_class_functions[m_idx]; - // Private methods are not accessible outside the class + // Private class methods are not accessible outside the class if (*name == '_' && !inside_class(cctx, cl)) { semsg(_(e_cannot_access_protected_method_str), fp->uf_name); @@ -2462,7 +2462,8 @@ compile_subscript( return FAIL; ppconst->pp_is_const = FALSE; - if ((type = get_type_on_stack(cctx, 0)) != &t_unknown + type = get_type_on_stack(cctx, 0); + if (type != &t_unknown && (type->tt_type == VAR_CLASS || type->tt_type == VAR_OBJECT)) { diff --git a/src/vim9type.c b/src/vim9type.c --- a/src/vim9type.c +++ b/src/vim9type.c @@ -719,7 +719,7 @@ check_typval_type(type_T *expected, typv if (expected == NULL) return OK; // didn't expect anything. - // + ga_init2(&type_list, sizeof(type_T *), 10); // A null_function and null_partial are special cases, they can be used to @@ -1739,8 +1739,15 @@ type_name(type_T *type, char **tofree) if (type->tt_type == VAR_OBJECT || type->tt_type == VAR_CLASS) { - char_u *class_name = type->tt_class == NULL ? (char_u *)"Unknown" - : type->tt_class->class_name; + char_u *class_name; + if (type->tt_class != NULL) + { + class_name = type->tt_class->class_name; + if (IS_ENUM(type->tt_class)) + name = "enum"; + } + else + class_name = (char_u *)"Unknown"; size_t len = STRLEN(name) + STRLEN(class_name) + 3; *tofree = alloc(len); if (*tofree != NULL) @@ -1869,18 +1876,26 @@ check_typval_is_value(typval_T *tv) { if (tv == NULL) return OK; - if (tv->v_type == VAR_CLASS) + + switch (tv->v_type) { - if (tv->vval.v_class != NULL) - semsg(_(e_using_class_as_value_str), tv->vval.v_class->class_name); - else - emsg(e_using_class_as_var_val); - return FAIL; - } - else if (tv->v_type == VAR_TYPEALIAS) - { - semsg(_(e_using_typealias_as_value_str), tv->vval.v_typealias->ta_name); - return FAIL; + case VAR_CLASS: + { + class_T *cl = tv->vval.v_class; + if (IS_ENUM(cl)) + semsg(_(e_using_enum_as_value_str), cl->class_name); + else + semsg(_(e_using_class_as_value_str), cl->class_name); + } + return FAIL; + + case VAR_TYPEALIAS: + semsg(_(e_using_typealias_as_value_str), + tv->vval.v_typealias->ta_name); + return FAIL; + + default: + break; } return OK; } @@ -1893,17 +1908,25 @@ check_type_is_value(type_T *type) { if (type == NULL) return OK; - if (type->tt_type == VAR_CLASS) + switch (type->tt_type) { - semsg(_(e_using_class_as_value_str), type->tt_class->class_name); - return FAIL; - } - else if (type->tt_type == VAR_TYPEALIAS) - { - // TODO: Not sure what could be done here to get a name. - // Maybe an optional argument? - emsg(_(e_using_typealias_as_var_val)); - return FAIL; + case VAR_CLASS: + if (IS_ENUM(type->tt_class)) + semsg(_(e_using_enum_as_value_str), + type->tt_class->class_name); + else + semsg(_(e_using_class_as_value_str), + type->tt_class->class_name); + return FAIL; + + case VAR_TYPEALIAS: + // TODO: Not sure what could be done here to get a name. + // Maybe an optional argument? + emsg(_(e_using_typealias_as_var_val)); + return FAIL; + + default: + break; } return OK; }