# HG changeset patch # User Christian Brabandt # Date 1696442406 -7200 # Node ID 508dfc0c261cf6b5d50b7a0c535d8e855571d2f1 # Parent 816fd36de6e0a0beab447a85a3b953578df1f8d6 patch 9.0.1977: Vim9: object members can change type Commit: https://github.com/vim/vim/commit/fe7b20a1a39dc645a6ea7ae925512f9227fd1695 Author: Yegappan Lakshmanan Date: Wed Oct 4 19:47:52 2023 +0200 patch 9.0.1977: Vim9: object members can change type Problem: Vim9: object members can change type Solution: Check type during assignment to object/class var closes: #13127 closes: #13262 Signed-off-by: Christian Brabandt Co-authored-by: Yegappan Lakshmanan diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -2,6 +2,7 @@ int object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl); void ex_class(exarg_T *eap); type_T *class_member_type(class_T *cl, int is_object, char_u *name, char_u *name_end, int *member_idx); +type_T *class_member_type_by_idx(class_T *cl, int is_object, int member_idx); void ex_enum(exarg_T *eap); void ex_type(exarg_T *eap); int class_object_index(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1509,10 +1509,11 @@ typedef enum { * Entry for an object or class member variable. */ typedef struct { - char_u *ocm_name; // allocated + char_u *ocm_name; // allocated omacc_T ocm_access; + int ocm_has_type; // type specified explicitly type_T *ocm_type; - char_u *ocm_init; // allocated + char_u *ocm_init; // allocated } ocmember_T; // used for the lookup table of a class member index and object method index diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -6418,4 +6418,390 @@ def Test_extended_obj_method_type_check( v9.CheckSourceFailure(lines, 'E1383: Method "Doit": type mismatch, expected func(object): object but got func(object): object', 20) enddef +" Test type checking for class variable in assignments +func Test_class_variable_complex_type_check() + " class variable with a specific type. Try assigning a different type at + " script level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public static Fn: func(list>): dict> = Foo + endclass + test_garbagecollect_now() + A.Fn = "abc" + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 9) + + " class variable with a specific type. Try assigning a different type at + " class def method level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public static Fn: func(list>): dict> = Foo + def Bar() + Fn = "abc" + enddef + endclass + var a = A.new() + test_garbagecollect_now() + a.Bar() + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 1) + + " class variable with a specific type. Try assigning a different type at + " script def method level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public static Fn: func(list>): dict> = Foo + endclass + def Bar() + A.Fn = "abc" + enddef + test_garbagecollect_now() + Bar() + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 1) + + " class variable without any type. Should be set to the initialization + " expression type. Try assigning a different type from script level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public static Fn = Foo + endclass + test_garbagecollect_now() + A.Fn = "abc" + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 9) + + " class variable without any type. Should be set to the initialization + " expression type. Try assigning a different type at class def level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public static Fn = Foo + def Bar() + Fn = "abc" + enddef + endclass + var a = A.new() + test_garbagecollect_now() + a.Bar() + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 1) + + " class variable without any type. Should be set to the initialization + " expression type. Try assigning a different type at script def level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public static Fn = Foo + endclass + def Bar() + A.Fn = "abc" + enddef + test_garbagecollect_now() + Bar() + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 1) + + " class variable with 'any" type. Can be assigned different types. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public static Fn: any = Foo + public static Fn2: any + endclass + test_garbagecollect_now() + assert_equal('func(list>): dict>', typename(A.Fn)) + A.Fn = "abc" + test_garbagecollect_now() + assert_equal('string', typename(A.Fn)) + A.Fn2 = Foo + test_garbagecollect_now() + assert_equal('func(list>): dict>', typename(A.Fn2)) + A.Fn2 = "xyz" + test_garbagecollect_now() + assert_equal('string', typename(A.Fn2)) + END + call v9.CheckSourceSuccess(lines) + + " class variable with 'any" type. Can be assigned different types. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public static Fn: any = Foo + public static Fn2: any + + def Bar() + assert_equal('func(list>): dict>', typename(Fn)) + Fn = "abc" + assert_equal('string', typename(Fn)) + Fn2 = Foo + assert_equal('func(list>): dict>', typename(Fn2)) + Fn2 = "xyz" + assert_equal('string', typename(Fn2)) + enddef + endclass + var a = A.new() + test_garbagecollect_now() + a.Bar() + test_garbagecollect_now() + A.Fn = Foo + a.Bar() + END + call v9.CheckSourceSuccess(lines) + + " class variable with 'any" type. Can be assigned different types. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public static Fn: any = Foo + public static Fn2: any + endclass + + def Bar() + assert_equal('func(list>): dict>', typename(A.Fn)) + A.Fn = "abc" + assert_equal('string', typename(A.Fn)) + A.Fn2 = Foo + assert_equal('func(list>): dict>', typename(A.Fn2)) + A.Fn2 = "xyz" + assert_equal('string', typename(A.Fn2)) + enddef + Bar() + test_garbagecollect_now() + A.Fn = Foo + Bar() + END + call v9.CheckSourceSuccess(lines) +endfunc + +" Test type checking for object variable in assignments +func Test_object_variable_complex_type_check() + " object variable with a specific type. Try assigning a different type at + " script level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public this.Fn: func(list>): dict> = Foo + endclass + var a = A.new() + test_garbagecollect_now() + a.Fn = "abc" + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 10) + + " object variable with a specific type. Try assigning a different type at + " object def method level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public this.Fn: func(list>): dict> = Foo + def Bar() + this.Fn = "abc" + this.Fn = Foo + enddef + endclass + var a = A.new() + test_garbagecollect_now() + a.Bar() + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 1) + + " object variable with a specific type. Try assigning a different type at + " script def method level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public this.Fn: func(list>): dict> = Foo + endclass + def Bar() + var a = A.new() + a.Fn = "abc" + a.Fn = Foo + enddef + test_garbagecollect_now() + Bar() + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 2) + + " object variable without any type. Should be set to the initialization + " expression type. Try assigning a different type from script level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public this.Fn = Foo + endclass + var a = A.new() + test_garbagecollect_now() + a.Fn = "abc" + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 10) + + " object variable without any type. Should be set to the initialization + " expression type. Try assigning a different type at object def level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public this.Fn = Foo + def Bar() + this.Fn = "abc" + this.Fn = Foo + enddef + endclass + var a = A.new() + test_garbagecollect_now() + a.Bar() + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 1) + + " object variable without any type. Should be set to the initialization + " expression type. Try assigning a different type at script def level. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public this.Fn = Foo + endclass + def Bar() + var a = A.new() + a.Fn = "abc" + a.Fn = Foo + enddef + test_garbagecollect_now() + Bar() + END + call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list>): dict> but got string', 2) + + " object variable with 'any" type. Can be assigned different types. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public this.Fn: any = Foo + public this.Fn2: any + endclass + + var a = A.new() + test_garbagecollect_now() + assert_equal('func(list>): dict>', typename(a.Fn)) + a.Fn = "abc" + test_garbagecollect_now() + assert_equal('string', typename(a.Fn)) + a.Fn2 = Foo + test_garbagecollect_now() + assert_equal('func(list>): dict>', typename(a.Fn2)) + a.Fn2 = "xyz" + test_garbagecollect_now() + assert_equal('string', typename(a.Fn2)) + END + call v9.CheckSourceSuccess(lines) + + " object variable with 'any" type. Can be assigned different types. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public this.Fn: any = Foo + public this.Fn2: any + + def Bar() + assert_equal('func(list>): dict>', typename(this.Fn)) + this.Fn = "abc" + assert_equal('string', typename(this.Fn)) + this.Fn2 = Foo + assert_equal('func(list>): dict>', typename(this.Fn2)) + this.Fn2 = "xyz" + assert_equal('string', typename(this.Fn2)) + enddef + endclass + + var a = A.new() + test_garbagecollect_now() + a.Bar() + test_garbagecollect_now() + a.Fn = Foo + a.Bar() + END + call v9.CheckSourceSuccess(lines) + + " object variable with 'any" type. Can be assigned different types. + let lines =<< trim END + vim9script + def Foo(l: list>): dict> + return {} + enddef + class A + public this.Fn: any = Foo + public this.Fn2: any + endclass + + def Bar() + var a = A.new() + assert_equal('func(list>): dict>', typename(a.Fn)) + a.Fn = "abc" + assert_equal('string', typename(a.Fn)) + a.Fn2 = Foo + assert_equal('func(list>): dict>', typename(a.Fn2)) + a.Fn2 = "xyz" + assert_equal('string', typename(a.Fn2)) + enddef + test_garbagecollect_now() + Bar() + test_garbagecollect_now() + Bar() + END + call v9.CheckSourceSuccess(lines) +endfunc + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1977, +/**/ 1976, /**/ 1975, diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -72,6 +72,7 @@ parse_member( char_u *varname, int has_public, // TRUE if "public" seen before "varname" char_u **varname_end, + int *has_type, garray_T *type_list, type_T **type_ret, char_u **init_expr) @@ -86,6 +87,7 @@ parse_member( char_u *colon = skipwhite(*varname_end); char_u *type_arg = colon; type_T *type = NULL; + *has_type = FALSE; if (*colon == ':') { if (VIM_ISWHITE(**varname_end)) @@ -102,6 +104,7 @@ parse_member( type = parse_type(&type_arg, type_list, TRUE); if (type == NULL) return FAIL; + *has_type = TRUE; } char_u *init_arg = skipwhite(type_arg); @@ -160,6 +163,7 @@ add_member( char_u *varname, char_u *varname_end, int has_public, + int has_type, type_T *type, char_u *init_expr) { @@ -169,6 +173,7 @@ add_member( m->ocm_name = vim_strnsave(varname, varname_end - varname); m->ocm_access = has_public ? VIM_ACCESS_ALL : *varname == '_' ? VIM_ACCESS_PRIVATE : VIM_ACCESS_READ; + m->ocm_has_type = has_type; m->ocm_type = type; if (init_expr != NULL) m->ocm_init = init_expr; @@ -1149,6 +1154,10 @@ add_lookup_tables(class_T *cl, class_T * static void add_class_members(class_T *cl, exarg_T *eap) { + garray_T type_list; + + ga_init2(&type_list, sizeof(type_T *), 10); + // Allocate a typval for each class member and initialize it. cl->class_members_tv = ALLOC_CLEAR_MULT(typval_T, cl->class_class_member_count); @@ -1164,6 +1173,13 @@ add_class_members(class_T *cl, exarg_T * typval_T *etv = eval_expr(m->ocm_init, eap); if (etv != NULL) { + if (m->ocm_type->tt_type == VAR_ANY + && !m->ocm_has_type + && etv->v_type != VAR_SPECIAL) + // If the member variable type is not yet set, then use + // the initialization expression type. + m->ocm_type = typval2type(etv, get_copyID(), &type_list, + TVTT_DO_MEMBER|TVTT_MORE_SPECIFIC); *tv = *etv; vim_free(etv); } @@ -1175,6 +1191,8 @@ add_class_members(class_T *cl, exarg_T * tv->vval.v_string = NULL; } } + + clear_type_list(&type_list); } /* @@ -1643,6 +1661,7 @@ early_ret: char_u *varname_end = NULL; type_T *type = NULL; char_u *init_expr = NULL; + int has_type = FALSE; if (!is_class && *varname == '_') { @@ -1653,7 +1672,7 @@ early_ret: } if (parse_member(eap, line, varname, has_public, - &varname_end, &type_list, &type, + &varname_end, &has_type, &type_list, &type, is_class ? &init_expr: NULL) == FAIL) break; if (is_reserved_varname(varname, varname_end)) @@ -1668,7 +1687,7 @@ early_ret: break; } if (add_member(&objmembers, varname, varname_end, - has_public, type, init_expr) == FAIL) + has_public, has_type, type, init_expr) == FAIL) { vim_free(init_expr); break; @@ -1776,12 +1795,14 @@ early_ret: // "static _varname" // "static varname" // "public static varname" - char_u *varname = p; - char_u *varname_end = NULL; - type_T *type = NULL; - char_u *init_expr = NULL; + char_u *varname = p; + char_u *varname_end = NULL; + int has_type = FALSE; + type_T *type = NULL; + char_u *init_expr = NULL; + if (parse_member(eap, line, varname, has_public, - &varname_end, &type_list, &type, + &varname_end, &has_type, &type_list, &type, is_class ? &init_expr : NULL) == FAIL) break; if (is_reserved_varname(varname, varname_end)) @@ -1796,7 +1817,7 @@ early_ret: break; } if (add_member(&classmembers, varname, varname_end, - has_public, type, init_expr) == FAIL) + has_public, has_type, type, init_expr) == FAIL) { vim_free(init_expr); break; @@ -2081,6 +2102,35 @@ class_member_type( } /* + * Given a class or object variable index, return the variable type + */ + type_T * +class_member_type_by_idx( + class_T *cl, + int is_object, + int member_idx) +{ + ocmember_T *m; + int member_count; + + if (is_object) + { + m = cl->class_obj_members; + member_count = cl->class_obj_member_count; + } + else + { + m = cl->class_class_members; + member_count = cl->class_class_member_count; + } + + if (member_idx >= member_count) + return NULL; + + return m[member_idx].ocm_type; +} + +/* * Handle ":enum" up to ":endenum". */ void diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1766,6 +1766,9 @@ compile_lhs( } lhs->lhs_dest = dest_class_member; lhs->lhs_class = cctx->ctx_ufunc->uf_class; + lhs->lhs_type = + class_member_type_by_idx(cctx->ctx_ufunc->uf_class, + FALSE, lhs->lhs_classmember_idx); } else { @@ -3308,7 +3311,15 @@ compile_def_function( } type_T *type = get_type_on_stack(&cctx, 0); - if (m->ocm_type->tt_type != type->tt_type) + if (m->ocm_type->tt_type == VAR_ANY + && !m->ocm_has_type + && type->tt_type != VAR_SPECIAL) + { + // If the member variable type is not yet set, then use + // the initialization expression type. + m->ocm_type = type; + } + else if (m->ocm_type->tt_type != type->tt_type) { // The type of the member initialization expression is // determined at run time. Add a runtime type check.