patch 9.0.1977: Vim9: object members can change type

Problem:  Vim9: object members can change type
Solution: Check type during assignment to object/class var

closes: #13127
closes: #13262

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
diff --git a/src/vim9class.c b/src/vim9class.c
index 885ac03..05d9afc 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -72,6 +72,7 @@
     char_u	*varname,
     int		has_public,	    // TRUE if "public" seen before "varname"
     char_u	**varname_end,
+    int		*has_type,
     garray_T	*type_list,
     type_T	**type_ret,
     char_u	**init_expr)
@@ -86,6 +87,7 @@
     char_u *colon = skipwhite(*varname_end);
     char_u *type_arg = colon;
     type_T *type = NULL;
+    *has_type = FALSE;
     if (*colon == ':')
     {
 	if (VIM_ISWHITE(**varname_end))
@@ -102,6 +104,7 @@
 	type = parse_type(&type_arg, type_list, TRUE);
 	if (type == NULL)
 	    return FAIL;
+	*has_type = TRUE;
     }
 
     char_u *init_arg = skipwhite(type_arg);
@@ -160,6 +163,7 @@
     char_u	*varname,
     char_u	*varname_end,
     int		has_public,
+    int		has_type,
     type_T	*type,
     char_u	*init_expr)
 {
@@ -169,6 +173,7 @@
     m->ocm_name = vim_strnsave(varname, varname_end - varname);
     m->ocm_access = has_public ? VIM_ACCESS_ALL
 		      : *varname == '_' ? VIM_ACCESS_PRIVATE : VIM_ACCESS_READ;
+    m->ocm_has_type = has_type;
     m->ocm_type = type;
     if (init_expr != NULL)
 	m->ocm_init = init_expr;
@@ -1149,6 +1154,10 @@
     static void
 add_class_members(class_T *cl, exarg_T *eap)
 {
+    garray_T	type_list;
+
+    ga_init2(&type_list, sizeof(type_T *), 10);
+
     // Allocate a typval for each class member and initialize it.
     cl->class_members_tv = ALLOC_CLEAR_MULT(typval_T,
 					    cl->class_class_member_count);
@@ -1164,6 +1173,13 @@
 	    typval_T *etv = eval_expr(m->ocm_init, eap);
 	    if (etv != NULL)
 	    {
+		if (m->ocm_type->tt_type == VAR_ANY
+			&& !m->ocm_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,
+				    TVTT_DO_MEMBER|TVTT_MORE_SPECIFIC);
 		*tv = *etv;
 		vim_free(etv);
 	    }
@@ -1175,6 +1191,8 @@
 	    tv->vval.v_string = NULL;
 	}
     }
+
+    clear_type_list(&type_list);
 }
 
 /*
@@ -1643,6 +1661,7 @@
 	    char_u *varname_end = NULL;
 	    type_T *type = NULL;
 	    char_u *init_expr = NULL;
+	    int	    has_type = FALSE;
 
 	    if (!is_class && *varname == '_')
 	    {
@@ -1653,7 +1672,7 @@
 	    }
 
 	    if (parse_member(eap, line, varname, has_public,
-			  &varname_end, &type_list, &type,
+			  &varname_end, &has_type, &type_list, &type,
 			  is_class ? &init_expr: NULL) == FAIL)
 		break;
 	    if (is_reserved_varname(varname, varname_end))
@@ -1668,7 +1687,7 @@
 		break;
 	    }
 	    if (add_member(&objmembers, varname, varname_end,
-					  has_public, type, init_expr) == FAIL)
+				has_public, has_type, type, init_expr) == FAIL)
 	    {
 		vim_free(init_expr);
 		break;
@@ -1776,12 +1795,14 @@
 	    //	"static _varname"
 	    //	"static varname"
 	    //	"public static varname"
-	    char_u *varname = p;
-	    char_u *varname_end = NULL;
-	    type_T *type = NULL;
-	    char_u *init_expr = NULL;
+	    char_u  *varname = p;
+	    char_u  *varname_end = NULL;
+	    int	    has_type = FALSE;
+	    type_T  *type = NULL;
+	    char_u  *init_expr = NULL;
+
 	    if (parse_member(eap, line, varname, has_public,
-		      &varname_end, &type_list, &type,
+		      &varname_end, &has_type, &type_list, &type,
 		      is_class ? &init_expr : NULL) == FAIL)
 		break;
 	    if (is_reserved_varname(varname, varname_end))
@@ -1796,7 +1817,7 @@
 		break;
 	    }
 	    if (add_member(&classmembers, varname, varname_end,
-				      has_public, type, init_expr) == FAIL)
+			      has_public, has_type, type, init_expr) == FAIL)
 	    {
 		vim_free(init_expr);
 		break;
@@ -2081,6 +2102,35 @@
 }
 
 /*
+ * Given a class or object variable index, return the variable type
+ */
+    type_T *
+class_member_type_by_idx(
+    class_T	*cl,
+    int		is_object,
+    int		member_idx)
+{
+    ocmember_T	*m;
+    int		member_count;
+
+    if (is_object)
+    {
+	m = cl->class_obj_members;
+	member_count = cl->class_obj_member_count;
+    }
+    else
+    {
+	m = cl->class_class_members;
+	member_count = cl->class_class_member_count;
+    }
+
+    if (member_idx >= member_count)
+	return NULL;
+
+    return m[member_idx].ocm_type;
+}
+
+/*
  * Handle ":enum" up to ":endenum".
  */
     void