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 <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/errors.h b/src/errors.h
index 5dccc63..18d3f0c 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3585,8 +3585,38 @@
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"));
+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
-// E1415 - E1499 unused (reserved for Vim9 class support)
+// 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
index 69b8374..2647e7a 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1119,7 +1119,18 @@
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 @@
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
index 14650ca..8c27986 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -11486,15 +11486,31 @@
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
index de9d5b2..f16d475 100644
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -159,6 +159,8 @@
{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 @@
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
index bd26e81..cb758aa 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -595,7 +595,7 @@
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
index 1ed175e..d3d3b99 100644
--- a/src/proto/vim9class.pro
+++ b/src/proto/vim9class.pro
@@ -3,6 +3,7 @@
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
index 47a0050..0d3f60a 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1562,9 +1562,10 @@
// 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 @@
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
index c4403e6..ab4d42a 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -40,6 +40,7 @@
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_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
index d6c55bf..f0c6f0c 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -2275,6 +2275,18 @@
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 @@
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 @@
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
index 0000000..6e10e0c
--- /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<number> = [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<Foo>', typename(Foo.apple))
+ assert_equal('enum<Foo>', 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<Foo> but got dict<any>', 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<Foo>', 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<Foo> but got number', 1)
+
+ # Use a List of enums
+ lines =<< trim END
+ vim9script
+ enum Planet
+ Mercury,
+ Venus,
+ Earth
+ endenum
+ var l1: list<Planet> = [Planet.Mercury, Planet.Venus]
+ assert_equal(Planet.Venus, l1[1])
+ def Fn()
+ var l2: list<Planet> = [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<Fruit>', typename(Fruit))
+ assert_equal('enum<Fruit>', 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<Fruit>', typename(a))
+ assert_equal('enum<Fruit>', 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<Foo> 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<Foo> 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<Foo> 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> = 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<A>
+ 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> = 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> = 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
index 0d731da..33de3a5 100644
--- a/src/testing.c
+++ b/src/testing.c
@@ -1091,9 +1091,8 @@
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 @@
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
index 62958f6..6a73719 100644
--- a/src/typval.c
+++ b/src/typval.c
@@ -266,7 +266,13 @@
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 @@
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
index 4523359..bad5a93 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 219,
+/**/
218,
/**/
217,
diff --git a/src/vim.h b/src/vim.h
index 63a3188..cb5accd 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2164,7 +2164,9 @@
#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 @@
#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
index 7227c3d..b640323 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -168,7 +168,8 @@
/*
* 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 @@
}
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 @@
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 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 @@
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 @@
if (m->ocm_flags & OCMFLAG_CONST)
item_lock(tv, DICT_MAXNEST, TRUE, TRUE);
}
+
+ return OK;
}
/*
@@ -1284,13 +1289,21 @@
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 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 @@
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 @@
if (loop == 2)
fp->uf_flags |= FC_OBJECT;
}
+
+ ga_clear(gap);
}
return OK;
@@ -1471,6 +1487,246 @@
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 @@
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 @@
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 @@
{
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 @@
}
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: <Tkey, Tentry>
@@ -1545,6 +1804,11 @@
// 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 @@
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 @@
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 @@
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 @@
}
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)
{
- if (STRNCMP(line, end_name, is_class ? 8 : 12) != 0)
+ end_name = "endclass";
+ shortlen = 4;
+ fullen = 8;
+ }
+ else if (is_enum)
+ {
+ 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 @@
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 @@
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 @@
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 @@
break;
}
- if (!is_class)
+ if (is_interface)
{
emsg(_(e_static_member_not_supported_in_interface));
break;
@@ -1812,7 +2156,7 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
ga_init2(&lines_to_free, sizeof(char_u *), 50);
int class_flags;
- if (is_class)
- class_flags = abstract_method ? CF_ABSTRACT_METHOD : CF_CLASS;
- else
+ if (is_interface)
class_flags = CF_INTERFACE;
+ else
+ class_flags = abstract_method ? CF_ABSTRACT_METHOD : CF_CLASS;
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 @@
{
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 @@
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 @@
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 @@
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 @@
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 @@
}
/*
- * Handle ":enum" up to ":endenum".
- */
- void
-ex_enum(exarg_T *eap UNUSED)
-{
- // TODO
-}
-
-/*
* Type aliases (:type)
*/
@@ -3334,8 +3684,14 @@
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 @@
garray_T ga;
ga_init2(&ga, 1, 50);
- ga_concat(&ga, (char_u *)"object of ");
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 ");
ga_concat(&ga, cl == NULL ? (char_u *)"[unknown]"
: cl->class_name);
if (cl != NULL)
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 8f67d8a..0a2b068 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1614,6 +1614,13 @@
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 @@
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 @@
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 @@
{
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 @@
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 @@
// 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
index 3e6aed0..053f617 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -3258,6 +3258,12 @@
++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
index 9d67aea..43b13d8 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -446,7 +446,7 @@
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 @@
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 @@
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
index 0d004c8..4d93ba7 100644
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -719,7 +719,7 @@
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 @@
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 @@
{
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 @@
{
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;
}