Skip to content

Commit

Permalink
patch 9.0.1959: Vim9: methods parameters and types are covariant
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
yegappan authored and chrisbra committed Sep 29, 2023
1 parent 900894b commit f3b68d4
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 27 deletions.
12 changes: 9 additions & 3 deletions runtime/doc/vim9class.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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*
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/proto/vim9class.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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 : */
11 changes: 8 additions & 3 deletions src/structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
76 changes: 76 additions & 0 deletions src/testdir/test_vim9_class.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions src/version.c
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1959,
/**/
1958,
/**/
Expand Down
45 changes: 28 additions & 17 deletions src/vim9class.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
}

Expand Down
24 changes: 21 additions & 3 deletions src/vim9type.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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)
Expand All @@ -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;
}

Expand Down

0 comments on commit f3b68d4

Please sign in to comment.