Skip to content

Commit f3b68d4

Browse files
yegappanchrisbra
authored andcommitted
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 <[email protected]> Co-authored-by: Yegappan Lakshmanan <[email protected]>
1 parent 900894b commit f3b68d4

7 files changed

Lines changed: 145 additions & 27 deletions

File tree

runtime/doc/vim9class.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,10 @@ If the type of a variable is not explicitly specified in a class, then it is
530530
set to "any" during class definition. When an object is instantiated from the
531531
class, then the type of the variable is set.
532532

533+
The following reserved keyword names cannot be used as an object or class
534+
variable name: "super", "this", "true", "false", "null", "null_blob",
535+
"null_dict", "null_function", "null_list", "null_partial", "null_string",
536+
"null_channel" and "null_job".
533537

534538
Extending a class ~
535539
*extends*
@@ -543,9 +547,11 @@ Object variables from the base class are all taken over by the child class. It
543547
is not possible to override them (unlike some other languages).
544548

545549
*E1356* *E1357* *E1358*
546-
Object methods of the base class can be overruled. The signature (arguments,
547-
argument types and return type) must be exactly the same. The method of the
548-
base class can be called by prefixing "super.".
550+
Object methods of the base class can be overruled. The number of arguments
551+
must be exactly the same. The method argument type can be a contra-variant
552+
type of the base class method argument type. The method return value type can
553+
be a covariant type of the base class method return value type. The method of
554+
the base class can be called by prefixing "super.".
549555

550556
*E1377*
551557
The access level of a method (public or private) in a child class should be

src/proto/vim9class.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ int object_free_nonref(int copyID);
2929
void method_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len);
3030
void member_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len);
3131
void f_instanceof(typval_T *argvars, typval_T *rettv);
32-
int class_instance_of(class_T *cl, class_T *other_cl);
32+
int class_instance_of(class_T *cl, class_T *other_cl, int covariance_check);
3333
/* vim: set ft=c : */

src/structs.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4798,14 +4798,19 @@ typedef enum {
47984798
WT_ARGUMENT,
47994799
WT_VARIABLE,
48004800
WT_MEMBER,
4801-
WT_METHOD,
4801+
WT_METHOD, // object method
4802+
WT_METHOD_ARG, // object method argument type
4803+
WT_METHOD_RETURN // object method return type
48024804
} wherekind_T;
48034805

4804-
// Struct used to pass to error messages about where the error happened.
4806+
// Struct used to pass the location of a type check. Used in error messages to
4807+
// indicate where the error happened. Also used for doing covariance type
4808+
// check for object method return type and contra-variance type check for
4809+
// object method arguments.
48054810
typedef struct {
48064811
char *wt_func_name; // function name or NULL
48074812
char wt_index; // argument or variable index, 0 means unknown
4808-
wherekind_T wt_kind; // "variable" when TRUE, "argument" otherwise
4813+
wherekind_T wt_kind; // type check location
48094814
} where_T;
48104815

48114816
#define WHERE_INIT {NULL, 0, WT_UNKNOWN}

src/testdir/test_vim9_class.vim

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6318,4 +6318,80 @@ def Test_reserved_varname()
63186318
endfor
63196319
enddef
63206320

6321+
" Test for checking the type of the arguments and the return value of a object
6322+
" method in an extended class.
6323+
def Test_extended_obj_method_type_check()
6324+
var lines =<< trim END
6325+
vim9script
6326+
6327+
class A
6328+
endclass
6329+
class B extends A
6330+
endclass
6331+
class C extends B
6332+
endclass
6333+
6334+
class Foo
6335+
def Doit(p: B): B
6336+
return B.new()
6337+
enddef
6338+
endclass
6339+
6340+
class Bar extends Foo
6341+
def Doit(p: A): C
6342+
return C.new()
6343+
enddef
6344+
endclass
6345+
END
6346+
v9.CheckSourceSuccess(lines)
6347+
6348+
lines =<< trim END
6349+
vim9script
6350+
6351+
class A
6352+
endclass
6353+
class B extends A
6354+
endclass
6355+
class C extends B
6356+
endclass
6357+
6358+
class Foo
6359+
def Doit(p: B): B
6360+
return B.new()
6361+
enddef
6362+
endclass
6363+
6364+
class Bar extends Foo
6365+
def Doit(p: C): B
6366+
return B.new()
6367+
enddef
6368+
endclass
6369+
END
6370+
v9.CheckSourceFailure(lines, 'E1383: Method "Doit": type mismatch, expected func(object<B>): object<B> but got func(object<C>): object<B>', 20)
6371+
6372+
lines =<< trim END
6373+
vim9script
6374+
6375+
class A
6376+
endclass
6377+
class B extends A
6378+
endclass
6379+
class C extends B
6380+
endclass
6381+
6382+
class Foo
6383+
def Doit(p: B): B
6384+
return B.new()
6385+
enddef
6386+
endclass
6387+
6388+
class Bar extends Foo
6389+
def Doit(p: B): A
6390+
return A.new()
6391+
enddef
6392+
endclass
6393+
END
6394+
v9.CheckSourceFailure(lines, 'E1383: Method "Doit": type mismatch, expected func(object<B>): object<B> but got func(object<B>): object<A>', 20)
6395+
enddef
6396+
63216397
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,8 @@ static char *(features[]) =
699699

700700
static int included_patches[] =
701701
{ /* Add new patch number below this line */
702+
/**/
703+
1959,
702704
/**/
703705
1958,
704706
/**/

src/vim9class.c

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2561,7 +2561,7 @@ inside_class(cctx_T *cctx_arg, class_T *cl)
25612561
{
25622562
for (cctx_T *cctx = cctx_arg; cctx != NULL; cctx = cctx->ctx_outer)
25632563
if (cctx->ctx_ufunc != NULL
2564-
&& class_instance_of(cctx->ctx_ufunc->uf_class, cl))
2564+
&& class_instance_of(cctx->ctx_ufunc->uf_class, cl, TRUE))
25652565
return TRUE;
25662566
return FALSE;
25672567
}
@@ -2871,29 +2871,39 @@ member_not_found_msg(class_T *cl, vartype_T v_type, char_u *name, size_t len)
28712871
* interfaces matches the class "other_cl".
28722872
*/
28732873
int
2874-
class_instance_of(class_T *cl, class_T *other_cl)
2874+
class_instance_of(class_T *cl, class_T *other_cl, int covariance_check)
28752875
{
28762876
if (cl == other_cl)
28772877
return TRUE;
28782878

2879-
// Recursively check the base classes.
2880-
for (; cl != NULL; cl = cl->class_extends)
2879+
if (covariance_check)
28812880
{
2882-
if (cl == other_cl)
2883-
return TRUE;
2884-
// Check the implemented interfaces and the super interfaces
2885-
for (int i = cl->class_interface_count - 1; i >= 0; --i)
2881+
// Recursively check the base classes.
2882+
for (; cl != NULL; cl = cl->class_extends)
28862883
{
2887-
class_T *intf = cl->class_interfaces_cl[i];
2888-
while (intf != NULL)
2884+
if (cl == other_cl)
2885+
return TRUE;
2886+
// Check the implemented interfaces and the super interfaces
2887+
for (int i = cl->class_interface_count - 1; i >= 0; --i)
28892888
{
2890-
if (intf == other_cl)
2891-
return TRUE;
2892-
// check the super interfaces
2893-
intf = intf->class_extends;
2889+
class_T *intf = cl->class_interfaces_cl[i];
2890+
while (intf != NULL)
2891+
{
2892+
if (intf == other_cl)
2893+
return TRUE;
2894+
// check the super interfaces
2895+
intf = intf->class_extends;
2896+
}
28942897
}
28952898
}
28962899
}
2900+
else
2901+
{
2902+
// contra-variance
2903+
for (; other_cl != NULL; other_cl = other_cl->class_extends)
2904+
if (cl == other_cl)
2905+
return TRUE;
2906+
}
28972907

28982908
return FALSE;
28992909
}
@@ -2928,7 +2938,7 @@ f_instanceof(typval_T *argvars, typval_T *rettv)
29282938
}
29292939

29302940
if (class_instance_of(object_tv->vval.v_object->obj_class,
2931-
li->li_tv.vval.v_class) == TRUE)
2941+
li->li_tv.vval.v_class, TRUE) == TRUE)
29322942
{
29332943
rettv->vval.v_number = VVAL_TRUE;
29342944
return;
@@ -2937,8 +2947,9 @@ f_instanceof(typval_T *argvars, typval_T *rettv)
29372947
}
29382948
else if (classinfo_tv->v_type == VAR_CLASS)
29392949
{
2940-
rettv->vval.v_number = class_instance_of(object_tv->vval.v_object->obj_class,
2941-
classinfo_tv->vval.v_class);
2950+
rettv->vval.v_number = class_instance_of(
2951+
object_tv->vval.v_object->obj_class,
2952+
classinfo_tv->vval.v_class, TRUE);
29422953
}
29432954
}
29442955

src/vim9type.c

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,8 @@ type_mismatch_where(type_T *expected, type_T *actual, where_T where)
759759
where.wt_func_name, typename1, typename2);
760760
break;
761761
case WT_METHOD:
762+
case WT_METHOD_ARG:
763+
case WT_METHOD_RETURN:
762764
semsg(_(e_method_str_type_mismatch_expected_str_but_got_str),
763765
where.wt_func_name, typename1, typename2);
764766
break;
@@ -869,8 +871,15 @@ check_type_maybe(
869871
{
870872
if (actual->tt_member != NULL
871873
&& actual->tt_member != &t_unknown)
874+
{
875+
where_T func_where = where;
876+
877+
if (where.wt_kind == WT_METHOD)
878+
func_where.wt_kind = WT_METHOD_RETURN;
872879
ret = check_type_maybe(expected->tt_member,
873-
actual->tt_member, FALSE, where);
880+
actual->tt_member, FALSE,
881+
func_where);
882+
}
874883
else
875884
ret = MAYBE;
876885
}
@@ -887,14 +896,20 @@ check_type_maybe(
887896

888897
for (i = 0; i < expected->tt_argcount
889898
&& i < actual->tt_argcount; ++i)
899+
{
900+
where_T func_where = where;
901+
if (where.wt_kind == WT_METHOD)
902+
func_where.wt_kind = WT_METHOD_ARG;
903+
890904
// Allow for using "any" argument type, lambda's have them.
891905
if (actual->tt_args[i] != &t_any && check_type(
892906
expected->tt_args[i], actual->tt_args[i], FALSE,
893-
where) == FAIL)
907+
func_where) == FAIL)
894908
{
895909
ret = FAIL;
896910
break;
897911
}
912+
}
898913
}
899914
if (ret == OK && expected->tt_argcount >= 0
900915
&& actual->tt_argcount == -1)
@@ -910,7 +925,10 @@ check_type_maybe(
910925
if (actual->tt_class == NULL)
911926
return OK; // A null object matches
912927

913-
if (class_instance_of(actual->tt_class, expected->tt_class) == FALSE)
928+
// For object method arguments, do a contra-variance type check in
929+
// an extended class. For all others, do a co-variance type check.
930+
if (class_instance_of(actual->tt_class, expected->tt_class,
931+
where.wt_kind != WT_METHOD_ARG) == FALSE)
914932
ret = FAIL;
915933
}
916934

0 commit comments

Comments
 (0)