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/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)