diff src/vim9class.c @ 33217:499ba27ba0f6 v9.0.1885

patch 9.0.1885: Vim9: no support for abstract methods Commit: https://github.com/vim/vim/commit/7bcd25cad3e9d5c9e25c7ae2bde67285c26f73cd Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Fri Sep 8 19:27:51 2023 +0200 patch 9.0.1885: Vim9: no support for abstract methods Problem: Vim9: no support for abstract methods Solution: Add support for defining abstract methods in an abstract class closes: #13044 closes: #13046 Signed-off-by: Christian Brabandt <cb@256bit.org> Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
author Christian Brabandt <cb@256bit.org>
date Fri, 08 Sep 2023 19:45:03 +0200
parents 71a097aab64d
children 52b121d4feb5
line wrap: on
line diff
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -308,21 +308,19 @@ validate_extends_class(char_u *extends_n
 	semsg(_(e_class_name_not_found_str), extends_name);
 	return success;
     }
+
+    if (tv.v_type != VAR_CLASS
+	    || tv.vval.v_class == NULL
+	    || (tv.vval.v_class->class_flags & CLASS_INTERFACE) != 0)
+	semsg(_(e_cannot_extend_str), extends_name);
     else
     {
-	if (tv.v_type != VAR_CLASS
-		|| tv.vval.v_class == NULL
-		|| (tv.vval.v_class->class_flags & CLASS_INTERFACE) != 0)
-	    semsg(_(e_cannot_extend_str), extends_name);
-	else
-	{
-	    class_T *extends_cl = tv.vval.v_class;
-	    ++extends_cl->class_refcount;
-	    *extends_clp = extends_cl;
-	    success = TRUE;
-	}
-	clear_tv(&tv);
+	class_T *extends_cl = tv.vval.v_class;
+	++extends_cl->class_refcount;
+	*extends_clp = extends_cl;
+	success = TRUE;
     }
+    clear_tv(&tv);
 
     return success;
 }
@@ -392,6 +390,65 @@ validate_extends_members(
 }
 
 /*
+ * When extending an abstract class, check whether all the abstract methods in
+ * the parent class are implemented.  Returns TRUE if all the methods are
+ * implemented.
+ */
+    static int
+validate_extends_methods(
+    garray_T	*classmethods_gap,
+    garray_T	*objmethods_gap,
+    class_T	*extends_cl)
+{
+    for (int loop = 1; loop <= 2; ++loop)
+    {
+	// loop == 1: check class methods
+	// loop == 2: check object methods
+	int extends_method_count = loop == 1
+				? extends_cl->class_class_function_count
+				: extends_cl->class_obj_method_count;
+	if (extends_method_count == 0)
+	    continue;
+
+	ufunc_T **extends_methods = loop == 1
+				? extends_cl->class_class_functions
+				: extends_cl->class_obj_methods;
+
+	int method_count = loop == 1 ? classmethods_gap->ga_len
+						: objmethods_gap->ga_len;
+	ufunc_T **cl_fp = (ufunc_T **)(loop == 1
+						? classmethods_gap->ga_data
+						: objmethods_gap->ga_data);
+
+	for (int i = 0; i < extends_method_count; i++)
+	{
+	    ufunc_T *uf = extends_methods[i];
+	    if ((uf->uf_flags & FC_ABSTRACT) == 0)
+		continue;
+
+	    int method_found = FALSE;
+
+	    for (int j = 0; j < method_count; j++)
+	    {
+		if (STRCMP(uf->uf_name, cl_fp[j]->uf_name) == 0)
+		{
+		    method_found = TRUE;
+		    break;
+		}
+	    }
+
+	    if (!method_found)
+	    {
+		semsg(_(e_abstract_method_str_not_found), uf->uf_name);
+		return FALSE;
+	    }
+	}
+    }
+
+    return TRUE;
+}
+
+/*
  * Check the members of the interface class "ifcl" match the class members
  * ("classmembers_gap") and object members ("objmembers_gap") of a class.
  * Returns TRUE if the class and object member names are valid.
@@ -1266,6 +1323,31 @@ early_ret:
 	    }
 	}
 
+	int abstract_method = FALSE;
+	char_u *pa = p;
+	if (checkforcmd(&p, "abstract", 3))
+	{
+	    if (STRNCMP(pa, "abstract", 8) != 0)
+	    {
+		semsg(_(e_command_cannot_be_shortened_str), pa);
+		break;
+	    }
+
+	    if (!is_abstract)
+	    {
+		semsg(_(e_abstract_method_in_concrete_class), pa);
+		break;
+	    }
+
+	    abstract_method = TRUE;
+	    p = skipwhite(pa + 8);
+	    if (STRNCMP(p, "def", 3) != 0 && STRNCMP(p, "static", 6) != 0)
+	    {
+		emsg(_(e_abstract_must_be_followed_by_def_or_static));
+		break;
+	    }
+	}
+
 	int has_static = FALSE;
 	char_u *ps = p;
 	if (checkforcmd(&p, "static", 4))
@@ -1344,8 +1426,13 @@ early_ret:
 	    ea.cookie = eap->cookie;
 
 	    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
+		class_flags = CF_INTERFACE;
 	    ufunc_T *uf = define_function(&ea, NULL, &lines_to_free,
-					   is_class ? CF_CLASS : CF_INTERFACE);
+								class_flags);
 	    ga_clear_strings(&lines_to_free);
 
 	    if (uf != NULL)
@@ -1353,7 +1440,8 @@ early_ret:
 		char_u	*name = uf->uf_name;
 		int	is_new = STRNCMP(name, "new", 3) == 0;
 
-		if (is_new && !is_valid_constructor(uf, is_abstract, has_static))
+		if (is_new && !is_valid_constructor(uf, is_abstract,
+								has_static))
 		{
 		    func_clear_free(uf, FALSE);
 		    break;
@@ -1374,6 +1462,9 @@ early_ret:
 		    if (is_new)
 			uf->uf_flags |= FC_NEW;
 
+		    if (abstract_method)
+			uf->uf_flags |= FC_ABSTRACT;
+
 		    ((ufunc_T **)fgap->ga_data)[fgap->ga_len] = uf;
 		    ++fgap->ga_len;
 		}
@@ -1430,12 +1521,20 @@ early_ret:
 	success = validate_extends_class(extends, &extends_cl);
     VIM_CLEAR(extends);
 
-    // Check the new class members and object members doesn't duplicate the
+    // Check the new class members and object members are not duplicates of the
     // members in the extended class lineage.
     if (success && extends_cl != NULL)
 	success = validate_extends_members(&classmembers, &objmembers,
 								extends_cl);
 
+    // When extending an abstract class, make sure all the abstract methods in
+    // the parent class are implemented.  If the current class is an abstract
+    // class, then there is no need for this check.
+    if (success && !is_abstract && extends_cl != NULL
+				&& (extends_cl->class_flags & CLASS_ABSTRACT))
+	success = validate_extends_methods(&classfunctions, &objmethods,
+								extends_cl);
+
     class_T **intf_classes = NULL;
 
     // Check all "implements" entries are valid.
@@ -1463,6 +1562,8 @@ early_ret:
 	    goto cleanup;
 	if (!is_class)
 	    cl->class_flags = CLASS_INTERFACE;
+	else if (is_abstract)
+	    cl->class_flags = CLASS_ABSTRACT;
 
 	cl->class_refcount = 1;
 	cl->class_name = vim_strnsave(name_start, name_end - name_start);