summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2023-10-04 19:47:52 +0200
committerChristian Brabandt <cb@256bit.org>2023-10-04 19:47:52 +0200
commitfe7b20a1a39dc645a6ea7ae925512f9227fd1695 (patch)
treed7bdcc5c9d3617324dbbab0dd13ae44e64ebc0fd
parentb9a974df9e22c7b56d8faac3246fb5f80a9351d4 (diff)
patch 9.0.1977: Vim9: object members can change typev9.0.1977
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>
-rw-r--r--src/proto/vim9class.pro1
-rw-r--r--src/structs.h5
-rw-r--r--src/testdir/test_vim9_class.vim386
-rw-r--r--src/version.c2
-rw-r--r--src/vim9class.c66
-rw-r--r--src/vim9compile.c13
6 files changed, 462 insertions, 11 deletions
diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro
index 9edf35408e..f2e642ec70 100644
--- a/src/proto/vim9class.pro
+++ b/src/proto/vim9class.pro
@@ -2,6 +2,7 @@
int object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl);
void ex_class(exarg_T *eap);
type_T *class_member_type(class_T *cl, int is_object, char_u *name, char_u *name_end, int *member_idx);
+type_T *class_member_type_by_idx(class_T *cl, int is_object, int member_idx);
void ex_enum(exarg_T *eap);
void ex_type(exarg_T *eap);
int class_object_index(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose);
diff --git a/src/structs.h b/src/structs.h
index 8443a4211d..c7360a30cc 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1509,10 +1509,11 @@ typedef enum {
* Entry for an object or class member variable.
*/
typedef struct {
- char_u *ocm_name; // allocated
+ char_u *ocm_name; // allocated
omacc_T ocm_access;
+ int ocm_has_type; // type specified explicitly
type_T *ocm_type;
- char_u *ocm_init; // allocated
+ char_u *ocm_init; // allocated
} ocmember_T;
// used for the lookup table of a class member index and object method index
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index 4a92a3d3a2..2aa5c08339 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -6418,4 +6418,390 @@ def Test_extended_obj_method_type_check()
v9.CheckSourceFailure(lines, 'E1383: Method "Doit": type mismatch, expected func(object<B>): object<B> but got func(object<B>): object<A>', 20)
enddef
+" Test type checking for class variable in assignments
+func Test_class_variable_complex_type_check()
+ " class variable with a specific type. Try assigning a different type at
+ " script level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public static Fn: func(list<dict<number>>): dict<list<number>> = Foo
+ endclass
+ test_garbagecollect_now()
+ A.Fn = "abc"
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 9)
+
+ " class variable with a specific type. Try assigning a different type at
+ " class def method level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public static Fn: func(list<dict<number>>): dict<list<number>> = Foo
+ def Bar()
+ Fn = "abc"
+ enddef
+ endclass
+ var a = A.new()
+ test_garbagecollect_now()
+ a.Bar()
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 1)
+
+ " class variable with a specific type. Try assigning a different type at
+ " script def method level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public static Fn: func(list<dict<number>>): dict<list<number>> = Foo
+ endclass
+ def Bar()
+ A.Fn = "abc"
+ enddef
+ test_garbagecollect_now()
+ Bar()
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 1)
+
+ " class variable without any type. Should be set to the initialization
+ " expression type. Try assigning a different type from script level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public static Fn = Foo
+ endclass
+ test_garbagecollect_now()
+ A.Fn = "abc"
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 9)
+
+ " class variable without any type. Should be set to the initialization
+ " expression type. Try assigning a different type at class def level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public static Fn = Foo
+ def Bar()
+ Fn = "abc"
+ enddef
+ endclass
+ var a = A.new()
+ test_garbagecollect_now()
+ a.Bar()
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 1)
+
+ " class variable without any type. Should be set to the initialization
+ " expression type. Try assigning a different type at script def level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public static Fn = Foo
+ endclass
+ def Bar()
+ A.Fn = "abc"
+ enddef
+ test_garbagecollect_now()
+ Bar()
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 1)
+
+ " class variable with 'any" type. Can be assigned different types.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public static Fn: any = Foo
+ public static Fn2: any
+ endclass
+ test_garbagecollect_now()
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(A.Fn))
+ A.Fn = "abc"
+ test_garbagecollect_now()
+ assert_equal('string', typename(A.Fn))
+ A.Fn2 = Foo
+ test_garbagecollect_now()
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(A.Fn2))
+ A.Fn2 = "xyz"
+ test_garbagecollect_now()
+ assert_equal('string', typename(A.Fn2))
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " class variable with 'any" type. Can be assigned different types.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public static Fn: any = Foo
+ public static Fn2: any
+
+ def Bar()
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(Fn))
+ Fn = "abc"
+ assert_equal('string', typename(Fn))
+ Fn2 = Foo
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(Fn2))
+ Fn2 = "xyz"
+ assert_equal('string', typename(Fn2))
+ enddef
+ endclass
+ var a = A.new()
+ test_garbagecollect_now()
+ a.Bar()
+ test_garbagecollect_now()
+ A.Fn = Foo
+ a.Bar()
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " class variable with 'any" type. Can be assigned different types.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public static Fn: any = Foo
+ public static Fn2: any
+ endclass
+
+ def Bar()
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(A.Fn))
+ A.Fn = "abc"
+ assert_equal('string', typename(A.Fn))
+ A.Fn2 = Foo
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(A.Fn2))
+ A.Fn2 = "xyz"
+ assert_equal('string', typename(A.Fn2))
+ enddef
+ Bar()
+ test_garbagecollect_now()
+ A.Fn = Foo
+ Bar()
+ END
+ call v9.CheckSourceSuccess(lines)
+endfunc
+
+" Test type checking for object variable in assignments
+func Test_object_variable_complex_type_check()
+ " object variable with a specific type. Try assigning a different type at
+ " script level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public this.Fn: func(list<dict<number>>): dict<list<number>> = Foo
+ endclass
+ var a = A.new()
+ test_garbagecollect_now()
+ a.Fn = "abc"
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 10)
+
+ " object variable with a specific type. Try assigning a different type at
+ " object def method level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public this.Fn: func(list<dict<number>>): dict<list<number>> = Foo
+ def Bar()
+ this.Fn = "abc"
+ this.Fn = Foo
+ enddef
+ endclass
+ var a = A.new()
+ test_garbagecollect_now()
+ a.Bar()
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 1)
+
+ " object variable with a specific type. Try assigning a different type at
+ " script def method level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public this.Fn: func(list<dict<number>>): dict<list<number>> = Foo
+ endclass
+ def Bar()
+ var a = A.new()
+ a.Fn = "abc"
+ a.Fn = Foo
+ enddef
+ test_garbagecollect_now()
+ Bar()
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 2)
+
+ " object variable without any type. Should be set to the initialization
+ " expression type. Try assigning a different type from script level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public this.Fn = Foo
+ endclass
+ var a = A.new()
+ test_garbagecollect_now()
+ a.Fn = "abc"
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 10)
+
+ " object variable without any type. Should be set to the initialization
+ " expression type. Try assigning a different type at object def level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public this.Fn = Foo
+ def Bar()
+ this.Fn = "abc"
+ this.Fn = Foo
+ enddef
+ endclass
+ var a = A.new()
+ test_garbagecollect_now()
+ a.Bar()
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 1)
+
+ " object variable without any type. Should be set to the initialization
+ " expression type. Try assigning a different type at script def level.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public this.Fn = Foo
+ endclass
+ def Bar()
+ var a = A.new()
+ a.Fn = "abc"
+ a.Fn = Foo
+ enddef
+ test_garbagecollect_now()
+ Bar()
+ END
+ call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<number>>): dict<list<number>> but got string', 2)
+
+ " object variable with 'any" type. Can be assigned different types.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public this.Fn: any = Foo
+ public this.Fn2: any
+ endclass
+
+ var a = A.new()
+ test_garbagecollect_now()
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(a.Fn))
+ a.Fn = "abc"
+ test_garbagecollect_now()
+ assert_equal('string', typename(a.Fn))
+ a.Fn2 = Foo
+ test_garbagecollect_now()
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(a.Fn2))
+ a.Fn2 = "xyz"
+ test_garbagecollect_now()
+ assert_equal('string', typename(a.Fn2))
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " object variable with 'any" type. Can be assigned different types.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public this.Fn: any = Foo
+ public this.Fn2: any
+
+ def Bar()
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(this.Fn))
+ this.Fn = "abc"
+ assert_equal('string', typename(this.Fn))
+ this.Fn2 = Foo
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(this.Fn2))
+ this.Fn2 = "xyz"
+ assert_equal('string', typename(this.Fn2))
+ enddef
+ endclass
+
+ var a = A.new()
+ test_garbagecollect_now()
+ a.Bar()
+ test_garbagecollect_now()
+ a.Fn = Foo
+ a.Bar()
+ END
+ call v9.CheckSourceSuccess(lines)
+
+ " object variable with 'any" type. Can be assigned different types.
+ let lines =<< trim END
+ vim9script
+ def Foo(l: list<dict<number>>): dict<list<number>>
+ return {}
+ enddef
+ class A
+ public this.Fn: any = Foo
+ public this.Fn2: any
+ endclass
+
+ def Bar()
+ var a = A.new()
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(a.Fn))
+ a.Fn = "abc"
+ assert_equal('string', typename(a.Fn))
+ a.Fn2 = Foo
+ assert_equal('func(list<dict<number>>): dict<list<number>>', typename(a.Fn2))
+ a.Fn2 = "xyz"
+ assert_equal('string', typename(a.Fn2))
+ enddef
+ test_garbagecollect_now()
+ Bar()
+ test_garbagecollect_now()
+ Bar()
+ END
+ call v9.CheckSourceSuccess(lines)
+endfunc
+
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/version.c b/src/version.c
index 83cf515ecf..4d037b4721 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1977,
+/**/
1976,
/**/
1975,
diff --git a/src/vim9class.c b/src/vim9class.c
index 885ac0385c..05d9afcf29 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -72,6 +72,7 @@ parse_member(
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 @@ parse_member(
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 @@ parse_member(
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 @@ add_member(
char_u *varname,
char_u *varname_end,
int has_public,
+ int has_type,
type_T *type,
char_u *init_expr)
{
@@ -169,6 +173,7 @@ add_member(
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 @@ add_lookup_tables(class_T *cl, class_T *extends_cl, garray_T *objmethods_gap)
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 @@ add_class_members(class_T *cl, exarg_T *eap)
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 @@ add_class_members(class_T *cl, exarg_T *eap)
tv->vval.v_string = NULL;
}
}
+
+ clear_type_list(&type_list);
}
/*
@@ -1643,6 +1661,7 @@ early_ret:
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 @@ early_ret:
}
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 @@ early_ret:
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 @@ early_ret:
// "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 @@ early_ret:
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 @@ class_member_type(
}
/*
+ * 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
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 086a322caa..abe1e2fb50 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1766,6 +1766,9 @@ compile_lhs(
}
lhs->lhs_dest = dest_class_member;
lhs->lhs_class = cctx->ctx_ufunc->uf_class;
+ lhs->lhs_type =
+ class_member_type_by_idx(cctx->ctx_ufunc->uf_class,
+ FALSE, lhs->lhs_classmember_idx);
}
else
{
@@ -3308,7 +3311,15 @@ compile_def_function(
}
type_T *type = get_type_on_stack(&cctx, 0);
- if (m->ocm_type->tt_type != type->tt_type)
+ if (m->ocm_type->tt_type == VAR_ANY
+ && !m->ocm_has_type
+ && type->tt_type != VAR_SPECIAL)
+ {
+ // If the member variable type is not yet set, then use
+ // the initialization expression type.
+ m->ocm_type = type;
+ }
+ else if (m->ocm_type->tt_type != type->tt_type)
{
// The type of the member initialization expression is
// determined at run time. Add a runtime type check.