patch 9.1.1267: Vim9: no support for type list/dict<object<any>>
Problem: Vim9: no support for type list/dict<object<any>>
Solution: add proper support for t_object_any
(Yegappan Lakshmanan)
closes: #17025
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 125ba55..f2c6999 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -308,7 +308,7 @@
if (type->tt_type == VAR_OBJECT
|| type_any_or_unknown(type))
return OK;
- arg_type_mismatch(&t_object, type, context->arg_idx + 1);
+ arg_type_mismatch(&t_object_any, type, context->arg_idx + 1);
return FAIL;
}
diff --git a/src/globals.h b/src/globals.h
index 7e65af3..b902e03 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -540,8 +540,8 @@
#define t_super (static_types[84])
#define t_const_super (static_types[85])
-#define t_object (static_types[86])
-#define t_const_object (static_types[87])
+#define t_object_any (static_types[86])
+#define t_const_object_any (static_types[87])
#define t_class (static_types[88])
#define t_const_class (static_types[89])
@@ -731,7 +731,7 @@
{VAR_CLASS, 0, 0, TTFLAG_STATIC, &t_bool, NULL, NULL},
{VAR_CLASS, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, &t_bool, NULL, NULL},
- // 86: t_object
+ // 86: t_object_any
{VAR_OBJECT, 0, 0, TTFLAG_STATIC, NULL, NULL, NULL},
{VAR_OBJECT, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, NULL, NULL, NULL},
diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim
index 22f8bab..7d65cb6 100644
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -2430,7 +2430,7 @@
enddef
Bar()
END
- v9.CheckSourceScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected object<Unknown> but got string')
+ v9.CheckSourceScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected object<any> but got string')
lines =<< trim END
vim9script
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index 30a03cf..c6d93b2 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -564,7 +564,7 @@
lines =<< trim END
vim9script
assert_equal(12, type(null_class))
- assert_equal('class<Unknown>', typename(null_class))
+ assert_equal('class<any>', typename(null_class))
END
v9.CheckSourceSuccess(lines)
enddef
@@ -643,7 +643,6 @@
END
v9.CheckSourceFailure(lines, 'E1360: Using a null object', 1)
- # TODO: this should not give an error but be handled at runtime
lines =<< trim END
vim9script
@@ -660,7 +659,7 @@
enddef
Func()
END
- v9.CheckSourceFailure(lines, 'E1363: Incomplete type', 1)
+ v9.CheckSourceFailure(lines, 'E1360: Using a null object', 1)
# Reference a object variable through a null class object which is stored in a
# variable of type "any".
@@ -710,7 +709,7 @@
def F(): any
return nullo
enddef
- assert_equal('object<Unknown>', typename(F()))
+ assert_equal('object<any>', typename(F()))
var o0 = F()
assert_true(o0 == null_object)
@@ -12486,37 +12485,325 @@
var lines =<< trim END
vim9script
- class C
+ class A
+ var n: list<number> = [100, 101]
def F(): string
- return 'C.F'
+ return 'A.F'
enddef
endclass
- class D
- var x: string
- def new(this.x)
+ class B
+ var name: string
+ var n: list<number> = [200, 201]
+ def new(this.name)
+ enddef
+ def F(): string
+ return 'B.F'
+ enddef
+ endclass
+
+ var obj1 = A.new()
+ var obj2 = B.new('b1')
+
+ def CheckObjectList()
+ var objlist = [obj1, obj2]
+ assert_equal('list<object<any>>', typename(objlist))
+
+ # Use a member function
+ assert_equal('A.F', objlist[0].F())
+ assert_equal('B.F', objlist[1].F())
+
+ # Use a member variable on the RHS
+ assert_equal([100, 101], objlist[0].n)
+ assert_equal([200, 201], objlist[1].n)
+
+ # Use a member variable on the LHS
+ objlist[0].n[1] = 110
+ objlist[1].n[1] = 210
+ assert_equal([100, 110], objlist[0].n)
+ assert_equal([200, 210], objlist[1].n)
+
+ # Iterate using a for loop
+ var s1 = []
+ for o in objlist
+ add(s1, o.F())
+ endfor
+ assert_equal(['A.F', 'B.F'], s1)
+
+ # Iterate using foreach()
+ var s2 = []
+ foreach(objlist, (k, v) => add(s2, v.F()))
+ assert_equal(['A.F', 'B.F'], s2)
+
+ # Add a new list item
+ objlist->add(B.new('b2'))
+ assert_equal('b2', objlist[2].name)
enddef
- def F(): string
- return 'D.F'
+
+ CheckObjectList()
+
+ var objlist = [A.new(), B.new('b2')]
+ assert_equal('list<object<any>>', typename(objlist))
+
+ # Use a member function
+ assert_equal('A.F', objlist[0].F())
+ assert_equal('B.F', objlist[1].F())
+
+ # Use a member variable on the RHS
+ assert_equal([100, 101], objlist[0].n)
+ assert_equal([200, 201], objlist[1].n)
+
+ # Use a member variable on the LHS
+ objlist[0].n[1] = 110
+ objlist[1].n[1] = 210
+ assert_equal([100, 110], objlist[0].n)
+ assert_equal([200, 210], objlist[1].n)
+
+ # Iterate using a for loop
+ var s1 = []
+ for o in objlist
+ add(s1, o.F())
+ endfor
+ assert_equal(['A.F', 'B.F'], s1)
+
+ # Iterate using foreach()
+ var s2 = []
+ foreach(objlist, (k, v) => add(s2, v.F()))
+ assert_equal(['A.F', 'B.F'], s2)
+
+ # Add a new list item
+ objlist->add(B.new('b2'))
+ assert_equal('b2', objlist[2].name)
+ END
+ v9.CheckSourceSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ class A
+ endclass
+
+ class B
+ endclass
+
+ var objlist = [A.new(), B.new()]
+ def Fn()
+ objlist->add(10)
enddef
- endclass
- var obj1 = C.new()
- var obj2 = D.new('a')
+ try
+ Fn()
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected object<any> but got number')
+ endtry
- def CheckObjectList()
- var items = [obj1, obj2]
- assert_equal('list<any>', typename(items))
- assert_equal('C.F', items[0].F())
- assert_equal('D.F', items[1].F())
- enddef
+ try
+ objlist->add(10)
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected object<any> but got number')
+ endtry
+ END
+ v9.CheckSourceSuccess(lines)
- CheckObjectList()
+ # Adding an enum to a List of objects should fail
+ lines =<< trim END
+ vim9script
+ class A
+ endclass
+ class B
+ endclass
+ enum C
+ Red,
+ Green,
+ endenum
+ var items = [A.new(), B.new()]
+ def Fn()
+ items->add(C.Red)
+ enddef
- var items2 = [obj1, obj2]
- assert_equal('list<any>', typename(items2))
- assert_equal('C.F', items2[0].F())
- assert_equal('D.F', items2[1].F())
+ try
+ Fn()
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected object<any> but got enum<C>')
+ endtry
+
+ try
+ items->add(C.Green)
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected object<any> but got enum<C>')
+ endtry
+
+ var items2 = [C.Red, C.Green]
+ def Fn2()
+ items2->add(A.new())
+ enddef
+ try
+ Fn2()
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected enum<C> but got object<A>')
+ endtry
+
+ try
+ items2->add(B.new())
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected enum<C> but got object<B>')
+ endtry
+ END
+ v9.CheckSourceSuccess(lines)
+enddef
+
+" Test for using a dict of objects
+def Test_dict_of_objects()
+ var lines =<< trim END
+ vim9script
+
+ class A
+ var n: list<number> = [100, 101]
+ def F(): string
+ return 'A.F'
+ enddef
+ endclass
+
+ class B
+ var name: string
+ var n: list<number> = [200, 201]
+ def new(this.name)
+ enddef
+ def F(): string
+ return 'B.F'
+ enddef
+ endclass
+
+ var obj1 = A.new()
+ var obj2 = B.new('b1')
+
+ def CheckObjectDict()
+ var objdict = {o_a: obj1, o_b: obj2}
+ assert_equal('dict<object<any>>', typename(objdict))
+
+ # Use a member function
+ assert_equal('A.F', objdict.o_a.F())
+ assert_equal('B.F', objdict.o_b.F())
+
+ # Use a member variable on the RHS
+ assert_equal([100, 101], objdict.o_a.n)
+ assert_equal([200, 201], objdict.o_b.n)
+
+ # Use a member variable on the LHS
+ objdict.o_a.n[1] = 110
+ objdict.o_b.n[1] = 210
+ assert_equal([100, 110], objdict.o_a.n)
+ assert_equal([200, 210], objdict.o_b.n)
+
+ # Iterate using a for loop
+ var l = []
+ for v in values(objdict)
+ add(l, v.F())
+ endfor
+ assert_equal(['A.F', 'B.F'], l)
+
+ # Iterate using foreach()
+ l = []
+ foreach(objdict, (k, v) => add(l, v.F()))
+ assert_equal(['A.F', 'B.F'], l)
+
+ # Add a new dict item
+ objdict['o_b2'] = B.new('b2')
+ assert_equal('b2', objdict.o_b2.name)
+ enddef
+
+ CheckObjectDict()
+
+ var objdict = {o_a: A.new(), o_b: B.new('b2')}
+ assert_equal('dict<object<any>>', typename(objdict))
+ assert_equal('A.F', objdict.o_a.F())
+ assert_equal('B.F', objdict.o_b.F())
+ assert_equal([100, 101], objdict.o_a.n)
+ assert_equal([200, 201], objdict.o_b.n)
+
+ var l = []
+ for v in values(objdict)
+ add(l, v.F())
+ endfor
+ assert_equal(['A.F', 'B.F'], l)
+
+ # Add a new dict item
+ objdict['o_b2'] = B.new('b2')
+ assert_equal('b2', objdict.o_b2.name)
+ END
+ v9.CheckSourceSuccess(lines)
+
+ lines =<< trim END
+ vim9script
+
+ class A
+ endclass
+ class B
+ endclass
+ class C
+ endclass
+ var objdict = {a: A.new(), b: B.new()}
+ def Fn()
+ objdict['c'] = C.new()
+ enddef
+
+ try
+ Fn()
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected object<any> but got number')
+ endtry
+
+ try
+ objdict['c'] = C.new()
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected object<any> but got number')
+ endtry
+ END
+ v9.CheckSourceSuccess(lines)
+
+ # Adding an enum to a Dict of objects should fail
+ lines =<< trim END
+ vim9script
+ class A
+ endclass
+ class B
+ endclass
+ enum C
+ Red,
+ Green,
+ endenum
+ var items = {o_a: A.new(), o_b: B.new()}
+ def Fn()
+ items['o_c'] = C.Red
+ enddef
+
+ try
+ Fn()
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected object<any> but got enum<C>')
+ endtry
+
+ try
+ items['o_c'] = C.Green
+ catch
+ assert_exception('Vim(var):E1012: Type mismatch; expected object<any> but got enum<C>')
+ endtry
+
+ var items2 = {red: C.Red, green: C.Green}
+ def Fn2()
+ items2['o_a'] = A.new()
+ enddef
+ try
+ Fn2()
+ catch
+ assert_exception('Vim(eval):E1012: Type mismatch; expected enum<C> but got object<A>')
+ endtry
+
+ try
+ items2['o_a'] = B.new()
+ catch
+ assert_exception('Vim(var):E1012: Type mismatch; expected enum<C> but got object<B>')
+ endtry
END
v9.CheckSourceSuccess(lines)
enddef
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index d9013dd..8456037 100644
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -5180,7 +5180,7 @@
[null_dict, 1, '{}', 4, 'dict<any>'],
[null_function, 1, "function('')", 2, 'func(...): unknown'],
[null_list, 1, '[]', 3, 'list<any>'],
- [null_object, 1, 'object of [unknown]', 13, 'object<Unknown>'],
+ [null_object, 1, 'object of [unknown]', 13, 'object<any>'],
[null_partial, 1, "function('')", 2, 'func(...): unknown'],
[null_string, 1, "''", 1, 'string']
]
diff --git a/src/version.c b/src/version.c
index c90c7b5..47104ef 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1267,
+/**/
1266,
/**/
1265,
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 34a78da..0a4c3fa 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2498,7 +2498,8 @@
: get_type_on_stack(cctx, 0);
if (lhs->lhs_type->tt_type == VAR_CLASS
- || lhs->lhs_type->tt_type == VAR_OBJECT)
+ || (lhs->lhs_type->tt_type == VAR_OBJECT
+ && lhs->lhs_type != &t_object_any))
{
// Check whether the class or object variable is modifiable
if (!lhs_class_member_modifiable(lhs, var_start, cctx))
@@ -2522,7 +2523,7 @@
return OK;
}
- return generate_loadvar(cctx, lhs);
+ return generate_loadvar(cctx, lhs);
}
/*
diff --git a/src/vim9expr.c b/src/vim9expr.c
index 68de736..0ceff0b 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -2684,7 +2684,8 @@
type = get_type_on_stack(cctx, 0);
if (type != &t_unknown
&& (type->tt_type == VAR_CLASS
- || type->tt_type == VAR_OBJECT))
+ || (type->tt_type == VAR_OBJECT
+ && type != &t_object_any)))
{
// class member: SomeClass.varname
// class method: SomeClass.SomeMethod()
diff --git a/src/vim9instr.c b/src/vim9instr.c
index 1b322f6..d4593ea 100644
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -756,7 +756,7 @@
generate_PUSHOBJ(cctx_T *cctx)
{
RETURN_OK_IF_SKIP(cctx);
- if (generate_instr_type(cctx, ISN_PUSHOBJ, &t_object) == NULL)
+ if (generate_instr_type(cctx, ISN_PUSHOBJ, &t_object_any) == NULL)
return FAIL;
return OK;
}
@@ -2142,7 +2142,8 @@
RETURN_OK_IF_SKIP(cctx);
- if (type->tt_type == VAR_ANY || type->tt_type == VAR_UNKNOWN)
+ if (type->tt_type == VAR_ANY || type->tt_type == VAR_UNKNOWN
+ || type == &t_object_any)
ret_type = &t_any;
else if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL)
{
@@ -2213,7 +2214,9 @@
// check for dict type
type = get_type_on_stack(cctx, 0);
if (type->tt_type != VAR_DICT
- && type->tt_type != VAR_ANY && type->tt_type != VAR_UNKNOWN)
+ && type->tt_type != VAR_OBJECT
+ && type->tt_type != VAR_ANY
+ && type->tt_type != VAR_UNKNOWN)
{
char *tofree;
diff --git a/src/vim9type.c b/src/vim9type.c
index 03a391a..2f85095 100644
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -764,7 +764,7 @@
if (tv->vval.v_object != NULL)
return &tv->vval.v_object->obj_class->class_object_type;
- return &t_object;
+ return &t_object_any;
}
/*
@@ -1307,8 +1307,11 @@
return MAYBE; // use runtime type check
if (actual->tt_type != VAR_OBJECT)
return FAIL; // don't use tt_class
- if (actual->tt_class == NULL)
- return OK; // A null object matches
+ if (actual->tt_class == NULL) // null object
+ return OK;
+ // t_object_any matches any object except for an enum item
+ if (expected == &t_object_any && !IS_ENUM(actual->tt_class))
+ return OK;
// For object method arguments, do a invariant type check in
// an extended class. For all others, do a covariance type check.
@@ -2122,6 +2125,11 @@
common_type_var_func(type1, type2, dest, type_gap);
return;
}
+ else if (type1->tt_type == VAR_OBJECT)
+ {
+ *dest = &t_object_any;
+ return;
+ }
}
*dest = &t_any;
@@ -2428,7 +2436,7 @@
name = "enum";
}
else
- class_name = (char_u *)"Unknown";
+ class_name = (char_u *)"any";
size_t len = STRLEN(name) + STRLEN(class_name) + 3;
*tofree = alloc(len);