mirror of
https://github.com/vim/vim
synced 2025-07-16 01:01:58 +00:00
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:
committed by
Christian Brabandt
parent
900894b09a
commit
f3b68d4759
@ -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
|
set to "any" during class definition. When an object is instantiated from the
|
||||||
class, then the type of the variable is set.
|
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 ~
|
Extending a class ~
|
||||||
*extends*
|
*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).
|
is not possible to override them (unlike some other languages).
|
||||||
|
|
||||||
*E1356* *E1357* *E1358*
|
*E1356* *E1357* *E1358*
|
||||||
Object methods of the base class can be overruled. The signature (arguments,
|
Object methods of the base class can be overruled. The number of arguments
|
||||||
argument types and return type) must be exactly the same. The method of the
|
must be exactly the same. The method argument type can be a contra-variant
|
||||||
base class can be called by prefixing "super.".
|
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*
|
*E1377*
|
||||||
The access level of a method (public or private) in a child class should be
|
The access level of a method (public or private) in a child class should be
|
||||||
|
@ -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 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 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);
|
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 : */
|
/* vim: set ft=c : */
|
||||||
|
@ -4798,14 +4798,19 @@ typedef enum {
|
|||||||
WT_ARGUMENT,
|
WT_ARGUMENT,
|
||||||
WT_VARIABLE,
|
WT_VARIABLE,
|
||||||
WT_MEMBER,
|
WT_MEMBER,
|
||||||
WT_METHOD,
|
WT_METHOD, // object method
|
||||||
|
WT_METHOD_ARG, // object method argument type
|
||||||
|
WT_METHOD_RETURN // object method return type
|
||||||
} wherekind_T;
|
} 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 {
|
typedef struct {
|
||||||
char *wt_func_name; // function name or NULL
|
char *wt_func_name; // function name or NULL
|
||||||
char wt_index; // argument or variable index, 0 means unknown
|
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;
|
} where_T;
|
||||||
|
|
||||||
#define WHERE_INIT {NULL, 0, WT_UNKNOWN}
|
#define WHERE_INIT {NULL, 0, WT_UNKNOWN}
|
||||||
|
@ -6318,4 +6318,80 @@ def Test_reserved_varname()
|
|||||||
endfor
|
endfor
|
||||||
enddef
|
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
|
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
|
||||||
|
@ -699,6 +699,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 */
|
||||||
|
/**/
|
||||||
|
1959,
|
||||||
/**/
|
/**/
|
||||||
1958,
|
1958,
|
||||||
/**/
|
/**/
|
||||||
|
@ -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)
|
for (cctx_T *cctx = cctx_arg; cctx != NULL; cctx = cctx->ctx_outer)
|
||||||
if (cctx->ctx_ufunc != NULL
|
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 TRUE;
|
||||||
return FALSE;
|
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".
|
* interfaces matches the class "other_cl".
|
||||||
*/
|
*/
|
||||||
int
|
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)
|
if (cl == other_cl)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
// Recursively check the base classes.
|
if (covariance_check)
|
||||||
for (; cl != NULL; cl = cl->class_extends)
|
|
||||||
{
|
{
|
||||||
if (cl == other_cl)
|
// Recursively check the base classes.
|
||||||
return TRUE;
|
for (; cl != NULL; cl = cl->class_extends)
|
||||||
// Check the implemented interfaces and the super interfaces
|
|
||||||
for (int i = cl->class_interface_count - 1; i >= 0; --i)
|
|
||||||
{
|
{
|
||||||
class_T *intf = cl->class_interfaces_cl[i];
|
if (cl == other_cl)
|
||||||
while (intf != NULL)
|
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)
|
class_T *intf = cl->class_interfaces_cl[i];
|
||||||
return TRUE;
|
while (intf != NULL)
|
||||||
// check the super interfaces
|
{
|
||||||
intf = intf->class_extends;
|
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;
|
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,
|
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;
|
rettv->vval.v_number = VVAL_TRUE;
|
||||||
return;
|
return;
|
||||||
@ -2937,8 +2947,9 @@ f_instanceof(typval_T *argvars, typval_T *rettv)
|
|||||||
}
|
}
|
||||||
else if (classinfo_tv->v_type == VAR_CLASS)
|
else if (classinfo_tv->v_type == VAR_CLASS)
|
||||||
{
|
{
|
||||||
rettv->vval.v_number = class_instance_of(object_tv->vval.v_object->obj_class,
|
rettv->vval.v_number = class_instance_of(
|
||||||
classinfo_tv->vval.v_class);
|
object_tv->vval.v_object->obj_class,
|
||||||
|
classinfo_tv->vval.v_class, TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -759,6 +759,8 @@ type_mismatch_where(type_T *expected, type_T *actual, where_T where)
|
|||||||
where.wt_func_name, typename1, typename2);
|
where.wt_func_name, typename1, typename2);
|
||||||
break;
|
break;
|
||||||
case WT_METHOD:
|
case WT_METHOD:
|
||||||
|
case WT_METHOD_ARG:
|
||||||
|
case WT_METHOD_RETURN:
|
||||||
semsg(_(e_method_str_type_mismatch_expected_str_but_got_str),
|
semsg(_(e_method_str_type_mismatch_expected_str_but_got_str),
|
||||||
where.wt_func_name, typename1, typename2);
|
where.wt_func_name, typename1, typename2);
|
||||||
break;
|
break;
|
||||||
@ -869,8 +871,15 @@ check_type_maybe(
|
|||||||
{
|
{
|
||||||
if (actual->tt_member != NULL
|
if (actual->tt_member != NULL
|
||||||
&& actual->tt_member != &t_unknown)
|
&& 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,
|
ret = check_type_maybe(expected->tt_member,
|
||||||
actual->tt_member, FALSE, where);
|
actual->tt_member, FALSE,
|
||||||
|
func_where);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
ret = MAYBE;
|
ret = MAYBE;
|
||||||
}
|
}
|
||||||
@ -887,14 +896,20 @@ check_type_maybe(
|
|||||||
|
|
||||||
for (i = 0; i < expected->tt_argcount
|
for (i = 0; i < expected->tt_argcount
|
||||||
&& i < actual->tt_argcount; ++i)
|
&& 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.
|
// Allow for using "any" argument type, lambda's have them.
|
||||||
if (actual->tt_args[i] != &t_any && check_type(
|
if (actual->tt_args[i] != &t_any && check_type(
|
||||||
expected->tt_args[i], actual->tt_args[i], FALSE,
|
expected->tt_args[i], actual->tt_args[i], FALSE,
|
||||||
where) == FAIL)
|
func_where) == FAIL)
|
||||||
{
|
{
|
||||||
ret = FAIL;
|
ret = FAIL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ret == OK && expected->tt_argcount >= 0
|
if (ret == OK && expected->tt_argcount >= 0
|
||||||
&& actual->tt_argcount == -1)
|
&& actual->tt_argcount == -1)
|
||||||
@ -910,7 +925,10 @@ check_type_maybe(
|
|||||||
if (actual->tt_class == NULL)
|
if (actual->tt_class == NULL)
|
||||||
return OK; // A null object matches
|
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;
|
ret = FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user