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>
This commit is contained in:
Yegappan Lakshmanan
2025-04-01 20:43:36 +02:00
committed by Christian Brabandt
parent 7b6add0b4a
commit de8f8f732a
10 changed files with 343 additions and 41 deletions

View File

@ -308,7 +308,7 @@ arg_object(type_T *type, type_T *decl_type UNUSED, argcontext_T *context)
if (type->tt_type == VAR_OBJECT if (type->tt_type == VAR_OBJECT
|| type_any_or_unknown(type)) || type_any_or_unknown(type))
return OK; 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; return FAIL;
} }

View File

@ -540,8 +540,8 @@ EXTERN int garbage_collect_at_exit INIT(= FALSE);
#define t_super (static_types[84]) #define t_super (static_types[84])
#define t_const_super (static_types[85]) #define t_const_super (static_types[85])
#define t_object (static_types[86]) #define t_object_any (static_types[86])
#define t_const_object (static_types[87]) #define t_const_object_any (static_types[87])
#define t_class (static_types[88]) #define t_class (static_types[88])
#define t_const_class (static_types[89]) #define t_const_class (static_types[89])
@ -731,7 +731,7 @@ EXTERN type_T static_types[96]
{VAR_CLASS, 0, 0, TTFLAG_STATIC, &t_bool, NULL, NULL}, {VAR_CLASS, 0, 0, TTFLAG_STATIC, &t_bool, NULL, NULL},
{VAR_CLASS, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, &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, NULL, NULL, NULL},
{VAR_OBJECT, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, NULL, NULL, NULL}, {VAR_OBJECT, 0, 0, TTFLAG_STATIC|TTFLAG_CONST, NULL, NULL, NULL},

View File

@ -2430,7 +2430,7 @@ def Test_instanceof()
enddef enddef
Bar() Bar()
END 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 lines =<< trim END
vim9script vim9script

View File

@ -564,7 +564,7 @@ def Test_using_null_class()
lines =<< trim END lines =<< trim END
vim9script vim9script
assert_equal(12, type(null_class)) assert_equal(12, type(null_class))
assert_equal('class<Unknown>', typename(null_class)) assert_equal('class<any>', typename(null_class))
END END
v9.CheckSourceSuccess(lines) v9.CheckSourceSuccess(lines)
enddef enddef
@ -643,7 +643,6 @@ def Test_object_not_set()
END END
v9.CheckSourceFailure(lines, 'E1360: Using a null object', 1) v9.CheckSourceFailure(lines, 'E1360: Using a null object', 1)
# TODO: this should not give an error but be handled at runtime
lines =<< trim END lines =<< trim END
vim9script vim9script
@ -660,7 +659,7 @@ def Test_object_not_set()
enddef enddef
Func() Func()
END 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 # Reference a object variable through a null class object which is stored in a
# variable of type "any". # variable of type "any".
@ -710,7 +709,7 @@ def Test_null_object_assign_compare()
def F(): any def F(): any
return nullo return nullo
enddef enddef
assert_equal('object<Unknown>', typename(F())) assert_equal('object<any>', typename(F()))
var o0 = F() var o0 = F()
assert_true(o0 == null_object) assert_true(o0 == null_object)
@ -12486,37 +12485,325 @@ def Test_method_call_from_list_of_objects()
var lines =<< trim END var lines =<< trim END
vim9script vim9script
class C class A
var n: list<number> = [100, 101]
def F(): string def F(): string
return 'C.F' return 'A.F'
enddef enddef
endclass endclass
class D class B
var x: string var name: string
def new(this.x) 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 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 enddef
endclass
var obj1 = C.new() try
var obj2 = D.new('a') Fn()
catch
assert_exception('Vim(eval):E1012: Type mismatch; expected object<any> but got number')
endtry
def CheckObjectList() try
var items = [obj1, obj2] objlist->add(10)
assert_equal('list<any>', typename(items)) catch
assert_equal('C.F', items[0].F()) assert_exception('Vim(eval):E1012: Type mismatch; expected object<any> but got number')
assert_equal('D.F', items[1].F()) endtry
enddef 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] try
assert_equal('list<any>', typename(items2)) Fn()
assert_equal('C.F', items2[0].F()) catch
assert_equal('D.F', items2[1].F()) 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 END
v9.CheckSourceSuccess(lines) v9.CheckSourceSuccess(lines)
enddef enddef

View File

@ -5180,7 +5180,7 @@ def Test_null_values()
[null_dict, 1, '{}', 4, 'dict<any>'], [null_dict, 1, '{}', 4, 'dict<any>'],
[null_function, 1, "function('')", 2, 'func(...): unknown'], [null_function, 1, "function('')", 2, 'func(...): unknown'],
[null_list, 1, '[]', 3, 'list<any>'], [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_partial, 1, "function('')", 2, 'func(...): unknown'],
[null_string, 1, "''", 1, 'string'] [null_string, 1, "''", 1, 'string']
] ]

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 */
/**/
1267,
/**/ /**/
1266, 1266,
/**/ /**/

View File

@ -2498,7 +2498,8 @@ compile_load_lhs(
: get_type_on_stack(cctx, 0); : get_type_on_stack(cctx, 0);
if (lhs->lhs_type->tt_type == VAR_CLASS 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 // Check whether the class or object variable is modifiable
if (!lhs_class_member_modifiable(lhs, var_start, cctx)) if (!lhs_class_member_modifiable(lhs, var_start, cctx))
@ -2522,7 +2523,7 @@ compile_load_lhs(
return OK; return OK;
} }
return generate_loadvar(cctx, lhs); return generate_loadvar(cctx, lhs);
} }
/* /*

View File

@ -2684,7 +2684,8 @@ compile_subscript(
type = get_type_on_stack(cctx, 0); type = get_type_on_stack(cctx, 0);
if (type != &t_unknown if (type != &t_unknown
&& (type->tt_type == VAR_CLASS && (type->tt_type == VAR_CLASS
|| type->tt_type == VAR_OBJECT)) || (type->tt_type == VAR_OBJECT
&& type != &t_object_any)))
{ {
// class member: SomeClass.varname // class member: SomeClass.varname
// class method: SomeClass.SomeMethod() // class method: SomeClass.SomeMethod()

View File

@ -756,7 +756,7 @@ generate_SETTYPE(
generate_PUSHOBJ(cctx_T *cctx) generate_PUSHOBJ(cctx_T *cctx)
{ {
RETURN_OK_IF_SKIP(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 FAIL;
return OK; return OK;
} }
@ -2142,7 +2142,8 @@ generate_PCALL(
RETURN_OK_IF_SKIP(cctx); 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; ret_type = &t_any;
else if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL) else if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL)
{ {
@ -2213,7 +2214,9 @@ generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len)
// check for dict type // check for dict type
type = get_type_on_stack(cctx, 0); type = get_type_on_stack(cctx, 0);
if (type->tt_type != VAR_DICT 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; char *tofree;

View File

@ -764,7 +764,7 @@ oc_typval2type(typval_T *tv)
if (tv->vval.v_object != NULL) if (tv->vval.v_object != NULL)
return &tv->vval.v_object->obj_class->class_object_type; return &tv->vval.v_object->obj_class->class_object_type;
return &t_object; return &t_object_any;
} }
/* /*
@ -1307,8 +1307,11 @@ check_type_maybe(
return MAYBE; // use runtime type check return MAYBE; // use runtime type check
if (actual->tt_type != VAR_OBJECT) if (actual->tt_type != VAR_OBJECT)
return FAIL; // don't use tt_class return FAIL; // don't use tt_class
if (actual->tt_class == NULL) if (actual->tt_class == NULL) // null object
return OK; // A null object matches 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 // For object method arguments, do a invariant type check in
// an extended class. For all others, do a covariance type check. // an extended class. For all others, do a covariance type check.
@ -2122,6 +2125,11 @@ common_type(type_T *type1, type_T *type2, type_T **dest, garray_T *type_gap)
common_type_var_func(type1, type2, dest, type_gap); common_type_var_func(type1, type2, dest, type_gap);
return; return;
} }
else if (type1->tt_type == VAR_OBJECT)
{
*dest = &t_object_any;
return;
}
} }
*dest = &t_any; *dest = &t_any;
@ -2428,7 +2436,7 @@ type_name_class_or_obj(char *name, type_T *type, char **tofree)
name = "enum"; name = "enum";
} }
else else
class_name = (char_u *)"Unknown"; class_name = (char_u *)"any";
size_t len = STRLEN(name) + STRLEN(class_name) + 3; size_t len = STRLEN(name) + STRLEN(class_name) + 3;
*tofree = alloc(len); *tofree = alloc(len);