0
0
mirror of https://github.com/vim/vim.git synced 2025-09-24 03:44:06 -04:00

patch 9.1.0160: Vim9: Add support for using a class type of itself in an object method

Problem:  Add support for using a class type of itself in an object
          method (thinca)
Solution: Vim9: Add support for using a class type of itself in an
          object method (Yegappan Lakshmanan)

fixes: #12369
closes: #14165

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yegappan Lakshmanan
2024-03-09 15:44:19 +01:00
committed by Christian Brabandt
parent b2ec0da080
commit 35b867b685
6 changed files with 126 additions and 54 deletions

View File

@@ -3830,7 +3830,7 @@ set_var(
* If the variable already exists and "is_const" is FALSE the value is updated. * If the variable already exists and "is_const" is FALSE the value is updated.
* Otherwise the variable is created. * Otherwise the variable is created.
*/ */
void int
set_var_const( set_var_const(
char_u *name, char_u *name,
scid_T sid, scid_T sid,
@@ -3854,6 +3854,7 @@ set_var_const(
int var_in_autoload = FALSE; int var_in_autoload = FALSE;
int flags = flags_arg; int flags = flags_arg;
int free_tv_arg = !copy; // free tv_arg if not used int free_tv_arg = !copy; // free tv_arg if not used
int rc = FAIL;
if (sid != 0) if (sid != 0)
{ {
@@ -4127,10 +4128,14 @@ set_var_const(
// values. // values.
item_lock(dest_tv, DICT_MAXNEST, TRUE, TRUE); item_lock(dest_tv, DICT_MAXNEST, TRUE, TRUE);
rc = OK;
failed: failed:
vim_free(name_tofree); vim_free(name_tofree);
if (free_tv_arg) if (free_tv_arg)
clear_tv(tv_arg); clear_tv(tv_arg);
return rc;
} }
/* /*

View File

@@ -77,7 +77,7 @@ void vars_clear_ext(hashtab_T *ht, int free_val);
void delete_var(hashtab_T *ht, hashitem_T *hi); void delete_var(hashtab_T *ht, hashitem_T *hi);
int before_set_vvar(char_u *varname, dictitem_T *di, typval_T *tv, int copy, int *type_error); int before_set_vvar(char_u *varname, dictitem_T *di, typval_T *tv, int copy, int *type_error);
void set_var(char_u *name, typval_T *tv, int copy); void set_var(char_u *name, typval_T *tv, int copy);
void set_var_const(char_u *name, scid_T sid, type_T *type_arg, typval_T *tv_arg, int copy, int flags_arg, int var_idx); int set_var_const(char_u *name, scid_T sid, type_T *type_arg, typval_T *tv_arg, int copy, int flags_arg, int var_idx);
int var_check_permission(dictitem_T *di, char_u *name); int var_check_permission(dictitem_T *di, char_u *name);
int var_check_ro(int flags, char_u *name, int use_gettext); int var_check_ro(int flags, char_u *name, int use_gettext);
int var_check_lock(int flags, char_u *name, int use_gettext); int var_check_lock(int flags, char_u *name, int use_gettext);

View File

@@ -301,7 +301,7 @@ func GetVimCommand(...)
let cmd .= ' --not-a-term' let cmd .= ' --not-a-term'
let cmd .= ' --gui-dialog-file guidialogfile' let cmd .= ' --gui-dialog-file guidialogfile'
" remove any environment variables " remove any environment variables
let cmd = substitute(cmd, '[A-Z_]*=\S\+ *', '', 'g') let cmd = substitute(cmd, '[A-Z_]\+=\S\+ *', '', 'g')
" If using valgrind, make sure every run uses a different log file. " If using valgrind, make sure every run uses a different log file.
if cmd =~ 'valgrind.*--log-file=' if cmd =~ 'valgrind.*--log-file='

View File

@@ -67,6 +67,22 @@ def Test_class_basic()
END END
v9.CheckSourceFailure(lines, "E488: Trailing characters: | echo 'done'", 3) v9.CheckSourceFailure(lines, "E488: Trailing characters: | echo 'done'", 3)
# Try to define a class with the same name as an existing variable
lines =<< trim END
vim9script
var Something: list<number> = [1]
class Thing
endclass
interface Api
endinterface
class Something extends Thing implements Api
var v1: string = ''
def Foo()
enddef
endclass
END
v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "Something"', 7)
# Use old "this." prefixed member variable declaration syntax (without initialization) # Use old "this." prefixed member variable declaration syntax (without initialization)
lines =<< trim END lines =<< trim END
vim9script vim9script
@@ -10272,4 +10288,65 @@ func Test_object_string()
call v9.CheckSourceSuccess(lines) call v9.CheckSourceSuccess(lines)
endfunc endfunc
" Test for using a class in the class definition
def Test_Ref_Class_Within_Same_Class()
var lines =<< trim END
vim9script
class A
var n: number = 0
def Equals(other: A): bool
return this.n == other.n
enddef
endclass
var a1 = A.new(10)
var a2 = A.new(10)
var a3 = A.new(20)
assert_equal(true, a1.Equals(a2))
assert_equal(false, a2.Equals(a3))
END
v9.CheckScriptSuccess(lines)
lines =<< trim END
vim9script
class Foo
var num: number
def Clone(): Foo
return Foo.new(this.num)
enddef
endclass
var f1 = Foo.new(1)
def F()
var f2: Foo = f1.Clone()
assert_equal(false, f2 is f1)
assert_equal(true, f2.num == f1.num)
enddef
F()
var f3: Foo = f1.Clone()
assert_equal(false, f3 is f1)
assert_equal(true, f3.num == f1.num)
END
v9.CheckScriptSuccess(lines)
# Test for trying to use a class to extend when defining the same class
lines =<< trim END
vim9script
class A extends A
endclass
END
v9.CheckScriptFailure(lines, 'E1354: Cannot extend A', 3)
# Test for trying to use a class to implement when defining the same class
lines =<< trim END
vim9script
class A implements A
endclass
END
v9.CheckScriptFailure(lines, 'E1347: Not a valid interface: A', 3)
enddef
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

View File

@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
160,
/**/ /**/
159, 159,
/**/ /**/

View File

@@ -208,7 +208,7 @@ add_member(
* "parent_count" is the number of members in the parent class * "parent_count" is the number of members in the parent class
* "members" will be set to the newly allocated array of members and * "members" will be set to the newly allocated array of members and
* "member_count" set to the number of members. * "member_count" set to the number of members.
* Returns OK or FAIL. * Returns OK on success and FAIL on memory allocation failure.
*/ */
static int static int
add_members_to_class( add_members_to_class(
@@ -301,6 +301,7 @@ object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl)
*/ */
static int static int
validate_extends_class( validate_extends_class(
class_T *cl,
char_u *extends_name, char_u *extends_name,
class_T **extends_clp, class_T **extends_clp,
int is_class) int is_class)
@@ -308,6 +309,12 @@ validate_extends_class(
typval_T tv; typval_T tv;
int success = FALSE; int success = FALSE;
if (STRCMP(cl->class_name, extends_name) == 0)
{
semsg(_(e_cannot_extend_str), extends_name);
return success;
}
tv.v_type = VAR_UNKNOWN; tv.v_type = VAR_UNKNOWN;
if (eval_variable_import(extends_name, &tv) == FAIL) if (eval_variable_import(extends_name, &tv) == FAIL)
{ {
@@ -1642,6 +1649,36 @@ early_ret:
garray_T objmethods; garray_T objmethods;
ga_init2(&objmethods, sizeof(ufunc_T *), 10); ga_init2(&objmethods, sizeof(ufunc_T *), 10);
class_T *cl = NULL;
class_T *extends_cl = NULL; // class from "extends" argument
class_T **intf_classes = NULL;
cl = ALLOC_CLEAR_ONE(class_T);
if (cl == NULL)
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);
if (cl->class_name == NULL)
goto cleanup;
// 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;
/* /*
* Go over the body of the class/interface until "endclass" or * Go over the body of the class/interface until "endclass" or
* "endinterface" is found. * "endinterface" is found.
@@ -1981,15 +2018,13 @@ early_ret:
} }
vim_free(theline); vim_free(theline);
class_T *extends_cl = NULL; // class from "extends" argument
/* /*
* Check a few things before defining the class. * Check a few things before defining the class.
*/ */
// Check the "extends" class is valid. // Check the "extends" class is valid.
if (success && extends != NULL) if (success && extends != NULL)
success = validate_extends_class(extends, &extends_cl, is_class); success = validate_extends_class(cl, extends, &extends_cl, is_class);
VIM_CLEAR(extends); VIM_CLEAR(extends);
// Check the new object methods to make sure their access (public or // Check the new object methods to make sure their access (public or
@@ -2016,8 +2051,6 @@ early_ret:
success = validate_abstract_class_methods(&classfunctions, success = validate_abstract_class_methods(&classfunctions,
&objmethods, extends_cl); &objmethods, extends_cl);
class_T **intf_classes = NULL;
// Check all "implements" entries are valid. // Check all "implements" entries are valid.
if (success && ga_impl.ga_len > 0) if (success && ga_impl.ga_len > 0)
{ {
@@ -2032,24 +2065,10 @@ early_ret:
success = check_func_arg_names(&classfunctions, &objmethods, success = check_func_arg_names(&classfunctions, &objmethods,
&classmembers); &classmembers);
class_T *cl = NULL;
if (success) if (success)
{ {
// "endclass" encountered without failures: Create the class. // "endclass" encountered without failures: Create the class.
cl = ALLOC_CLEAR_ONE(class_T);
if (cl == NULL)
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);
if (cl->class_name == NULL)
goto cleanup;
if (extends_cl != NULL) if (extends_cl != NULL)
{ {
cl->class_extends = extends_cl; cl->class_extends = extends_cl;
@@ -2136,41 +2155,10 @@ early_ret:
// TODO: // TODO:
// - Fill hashtab with object members and methods ? // - Fill hashtab with object members and methods ?
// 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;
set_var_const(cl->class_name, current_sctx.sc_sid,
NULL, &tv, FALSE, 0, 0);
return; return;
} }
cleanup: cleanup:
if (cl != NULL)
{
vim_free(cl->class_name);
vim_free(cl->class_class_functions);
if (cl->class_interfaces != NULL)
{
for (int i = 0; i < cl->class_interface_count; ++i)
vim_free(cl->class_interfaces[i]);
vim_free(cl->class_interfaces);
}
if (cl->class_interfaces_cl != NULL)
{
for (int i = 0; i < cl->class_interface_count; ++i)
class_unref(cl->class_interfaces_cl[i]);
vim_free(cl->class_interfaces_cl);
}
vim_free(cl->class_obj_members);
vim_free(cl->class_obj_methods);
vim_free(cl);
}
vim_free(extends); vim_free(extends);
class_unref(extends_cl); class_unref(extends_cl);