patch 9.0.1959: Vim9: methods parameters and types are covariant

Problem:  Vim9: methods parameters and types are covariant
Solution: Support contra-variant type check for object method arguments
          (similar to Dart).

closes: #12965
closes: #13221

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
Yegappan Lakshmanan
2023-09-29 22:50:02 +02:00
committed by Christian Brabandt
parent 900894b09a
commit f3b68d4759
7 changed files with 145 additions and 27 deletions

View File

@ -530,6 +530,10 @@ If the type of a variable is not explicitly specified in a class, then it is
set to "any" during class definition. When an object is instantiated from the
class, then the type of the variable is set.
The following reserved keyword names cannot be used as an object or class
variable name: "super", "this", "true", "false", "null", "null_blob",
"null_dict", "null_function", "null_list", "null_partial", "null_string",
"null_channel" and "null_job".
Extending a class ~
*extends*
@ -543,9 +547,11 @@ Object variables from the base class are all taken over by the child class. It
is not possible to override them (unlike some other languages).
*E1356* *E1357* *E1358*
Object methods of the base class can be overruled. The signature (arguments,
argument types and return type) must be exactly the same. The method of the
base class can be called by prefixing "super.".
Object methods of the base class can be overruled. The number of arguments
must be exactly the same. The method argument type can be a contra-variant
type of the base class method argument type. The method return value type can
be a covariant type of the base class method return value type. The method of
the base class can be called by prefixing "super.".
*E1377*
The access level of a method (public or private) in a child class should be

View File

@ -29,5 +29,5 @@ int object_free_nonref(int copyID);
void method_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len);
void member_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len);
void f_instanceof(typval_T *argvars, typval_T *rettv);
int class_instance_of(class_T *cl, class_T *other_cl);
int class_instance_of(class_T *cl, class_T *other_cl, int covariance_check);
/* vim: set ft=c : */

View File

@ -4798,14 +4798,19 @@ typedef enum {
WT_ARGUMENT,
WT_VARIABLE,
WT_MEMBER,
WT_METHOD,
WT_METHOD, // object method
WT_METHOD_ARG, // object method argument type
WT_METHOD_RETURN // object method return type
} wherekind_T;
// Struct used to pass to error messages about where the error happened.
// Struct used to pass the location of a type check. Used in error messages to
// indicate where the error happened. Also used for doing covariance type
// check for object method return type and contra-variance type check for
// object method arguments.
typedef struct {
char *wt_func_name; // function name or NULL
char wt_index; // argument or variable index, 0 means unknown
wherekind_T wt_kind; // "variable" when TRUE, "argument" otherwise
wherekind_T wt_kind; // type check location
} where_T;
#define WHERE_INIT {NULL, 0, WT_UNKNOWN}

View File

@ -6318,4 +6318,80 @@ def Test_reserved_varname()
endfor
enddef
" Test for checking the type of the arguments and the return value of a object
" method in an extended class.
def Test_extended_obj_method_type_check()
var lines =<< trim END
vim9script
class A
endclass
class B extends A
endclass
class C extends B
endclass
class Foo
def Doit(p: B): B
return B.new()
enddef
endclass
class Bar extends Foo
def Doit(p: A): C
return C.new()
enddef
endclass
END
v9.CheckSourceSuccess(lines)
lines =<< trim END
vim9script
class A
endclass
class B extends A
endclass
class C extends B
endclass
class Foo
def Doit(p: B): B
return B.new()
enddef
endclass
class Bar extends Foo
def Doit(p: C): B
return B.new()
enddef
endclass
END
v9.CheckSourceFailure(lines, 'E1383: Method "Doit": type mismatch, expected func(object<B>): object<B> but got func(object<C>): object<B>', 20)
lines =<< trim END
vim9script
class A
endclass
class B extends A
endclass
class C extends B
endclass
class Foo
def Doit(p: B): B
return B.new()
enddef
endclass
class Bar extends Foo
def Doit(p: B): A
return A.new()
enddef
endclass
END
v9.CheckSourceFailure(lines, 'E1383: Method "Doit": type mismatch, expected func(object<B>): object<B> but got func(object<B>): object<A>', 20)
enddef
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

View File

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

View File

@ -2561,7 +2561,7 @@ inside_class(cctx_T *cctx_arg, class_T *cl)
{
for (cctx_T *cctx = cctx_arg; cctx != NULL; cctx = cctx->ctx_outer)
if (cctx->ctx_ufunc != NULL
&& class_instance_of(cctx->ctx_ufunc->uf_class, cl))
&& class_instance_of(cctx->ctx_ufunc->uf_class, cl, TRUE))
return TRUE;
return FALSE;
}
@ -2871,29 +2871,39 @@ member_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len)
* interfaces matches the class "other_cl".
*/
int
class_instance_of(class_T *cl, class_T *other_cl)
class_instance_of(class_T *cl, class_T *other_cl, int covariance_check)
{
if (cl == other_cl)
return TRUE;
// Recursively check the base classes.
for (; cl != NULL; cl = cl->class_extends)
if (covariance_check)
{
if (cl == other_cl)
return TRUE;
// Check the implemented interfaces and the super interfaces
for (int i = cl->class_interface_count - 1; i >= 0; --i)
// Recursively check the base classes.
for (; cl != NULL; cl = cl->class_extends)
{
class_T *intf = cl->class_interfaces_cl[i];
while (intf != NULL)
if (cl == other_cl)
return TRUE;
// Check the implemented interfaces and the super interfaces
for (int i = cl->class_interface_count - 1; i >= 0; --i)
{
if (intf == other_cl)
return TRUE;
// check the super interfaces
intf = intf->class_extends;
class_T *intf = cl->class_interfaces_cl[i];
while (intf != NULL)
{
if (intf == other_cl)
return TRUE;
// check the super interfaces
intf = intf->class_extends;
}
}
}
}
else
{
// contra-variance
for (; other_cl != NULL; other_cl = other_cl->class_extends)
if (cl == other_cl)
return TRUE;
}
return FALSE;
}
@ -2928,7 +2938,7 @@ f_instanceof(typval_T *argvars, typval_T *rettv)
}
if (class_instance_of(object_tv->vval.v_object->obj_class,
li->li_tv.vval.v_class) == TRUE)
li->li_tv.vval.v_class, TRUE) == TRUE)
{
rettv->vval.v_number = VVAL_TRUE;
return;
@ -2937,8 +2947,9 @@ f_instanceof(typval_T *argvars, typval_T *rettv)
}
else if (classinfo_tv->v_type == VAR_CLASS)
{
rettv->vval.v_number = class_instance_of(object_tv->vval.v_object->obj_class,
classinfo_tv->vval.v_class);
rettv->vval.v_number = class_instance_of(
object_tv->vval.v_object->obj_class,
classinfo_tv->vval.v_class, TRUE);
}
}

View File

@ -759,6 +759,8 @@ type_mismatch_where(type_T *expected, type_T *actual, where_T where)
where.wt_func_name, typename1, typename2);
break;
case WT_METHOD:
case WT_METHOD_ARG:
case WT_METHOD_RETURN:
semsg(_(e_method_str_type_mismatch_expected_str_but_got_str),
where.wt_func_name, typename1, typename2);
break;
@ -869,8 +871,15 @@ check_type_maybe(
{
if (actual->tt_member != NULL
&& actual->tt_member != &t_unknown)
{
where_T func_where = where;
if (where.wt_kind == WT_METHOD)
func_where.wt_kind = WT_METHOD_RETURN;
ret = check_type_maybe(expected->tt_member,
actual->tt_member, FALSE, where);
actual->tt_member, FALSE,
func_where);
}
else
ret = MAYBE;
}
@ -887,14 +896,20 @@ check_type_maybe(
for (i = 0; i < expected->tt_argcount
&& i < actual->tt_argcount; ++i)
{
where_T func_where = where;
if (where.wt_kind == WT_METHOD)
func_where.wt_kind = WT_METHOD_ARG;
// Allow for using "any" argument type, lambda's have them.
if (actual->tt_args[i] != &t_any && check_type(
expected->tt_args[i], actual->tt_args[i], FALSE,
where) == FAIL)
func_where) == FAIL)
{
ret = FAIL;
break;
}
}
}
if (ret == OK && expected->tt_argcount >= 0
&& actual->tt_argcount == -1)
@ -910,7 +925,10 @@ check_type_maybe(
if (actual->tt_class == NULL)
return OK; // A null object matches
if (class_instance_of(actual->tt_class, expected->tt_class) == FALSE)
// For object method arguments, do a contra-variance type check in
// an extended class. For all others, do a co-variance type check.
if (class_instance_of(actual->tt_class, expected->tt_class,
where.wt_kind != WT_METHOD_ARG) == FALSE)
ret = FAIL;
}