# HG changeset patch # User Christian Brabandt # Date 1692733502 -7200 # Node ID d5c05e15cf8147b74c93266a3936c435a9c60937 # Parent a0266fbc89f587a8d7f56a42733d275a0cdb2d03 patch 9.0.1780: Vim9 type not defined during object creation Commit: https://github.com/vim/vim/commit/618e47d1cd93954bad26d47e5353b4f1432daa5e Author: Yegappan Lakshmanan Date: Tue Aug 22 21:29:28 2023 +0200 patch 9.0.1780: Vim9 type not defined during object creation Problem: Vim9 type not defined during object creation Solution: Define type during object creation and not during class definition, parse mulit-line member initializers, fix lock initialization If type is not specified for a member, set it during object creation instead of during class definition. Add a runtime type check for the object member initialization expression Also, while at it, when copying an object or class, make sure the lock is correctly initialized. And finally, parse multi-line member initializers correctly. closes: #11957 closes: #12868 closes: #12869 closes: #12881 Signed-off-by: Christian Brabandt Co-authored-by: Yegappan Lakshmanan Co-authored-by: LemonBoy diff --git a/runtime/doc/vim9class.txt b/runtime/doc/vim9class.txt --- a/runtime/doc/vim9class.txt +++ b/runtime/doc/vim9class.txt @@ -423,6 +423,12 @@ Each member and function name can be use define a function with the same name and different type of arguments. +Member Initialization ~ +If the type of a member 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 member is set. + + Extending a class ~ *extends* A class can extend one other class. *E1352* *E1353* *E1354* 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 @@ -210,6 +210,17 @@ def Test_class_basic() var v = a.Foo(,) END v9.CheckScriptFailure(lines, 'E15:') + + lines =<< trim END + vim9script + class A + this.y = { + X: 1 + } + endclass + var a = A.new() + END + v9.CheckScriptSuccess(lines) enddef def Test_class_defined_twice() @@ -668,14 +679,28 @@ def Test_class_object_member_inits() END v9.CheckScriptFailure(lines, 'E1022:') + # If the type is not specified for a member, then it should be set during + # object creation and not when defining the class. lines =<< trim END vim9script - class TextPosition - this.lnum = v:none + + var init_count = 0 + def Init(): string + init_count += 1 + return 'foo' + enddef + + class A + this.str1 = Init() + this.str2: string = Init() this.col = 1 endclass + + assert_equal(init_count, 0) + var a = A.new() + assert_equal(init_count, 2) END - v9.CheckScriptFailure(lines, 'E1330:') + v9.CheckScriptSuccess(lines) # Test for initializing an object member with an unknown variable/type lines =<< trim END @@ -683,8 +708,9 @@ def Test_class_object_member_inits() class A this.value = init_val endclass + var a = A.new() END - v9.CheckScriptFailureList(lines, ['E121:', 'E1329:']) + v9.CheckScriptFailure(lines, 'E1001:') enddef def Test_class_object_member_access() @@ -2625,4 +2651,67 @@ def Test_new_return_type() v9.CheckScriptFailure(lines, 'E1365:') enddef +" Test for checking a member initialization type at run time. +def Test_runtime_type_check_for_member_init() + var lines =<< trim END + vim9script + + var retnum: bool = false + + def F(): any + retnum = !retnum + if retnum + return 1 + else + return "hello" + endif + enddef + + class C + this._foo: bool = F() + endclass + + var c1 = C.new() + var c2 = C.new() + END + v9.CheckScriptFailure(lines, 'E1012:') +enddef + +" Test for locking a variable referring to an object and reassigning to another +" object. +def Test_object_lockvar() + var lines =<< trim END + vim9script + + class C + this.val: number + def new(this.val) + enddef + endclass + + var some_dict: dict = { a: C.new(1), b: C.new(2), c: C.new(3), } + lockvar 2 some_dict + + var current: C + current = some_dict['c'] + assert_equal(3, current.val) + current = some_dict['b'] + assert_equal(2, current.val) + + def F() + current = some_dict['c'] + enddef + + def G() + current = some_dict['b'] + enddef + + F() + assert_equal(3, current.val) + G() + assert_equal(2, current.val) + END + v9.CheckScriptSuccess(lines) +enddef + " 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 @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1780, +/**/ 1779, /**/ 1778, diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -67,66 +67,48 @@ parse_member( return FAIL; } - char_u *expr_start = skipwhite(type_arg); - char_u *expr_end = expr_start; - if (type == NULL && *expr_start != '=') + char_u *init_arg = skipwhite(type_arg); + if (type == NULL && *init_arg != '=') { emsg(_(e_type_or_initialization_required)); return FAIL; } - if (*expr_start == '=') + if (init_expr == NULL && *init_arg == '=') { - if (!VIM_ISWHITE(expr_start[-1]) || !VIM_ISWHITE(expr_start[1])) + emsg(_(e_cannot_initialize_member_in_interface)); + return FAIL; + } + + if (*init_arg == '=') + { + evalarg_T evalarg; + char_u *expr_start, *expr_end; + + if (!VIM_ISWHITE(init_arg[-1]) || !VIM_ISWHITE(init_arg[1])) { semsg(_(e_white_space_required_before_and_after_str_at_str), "=", type_arg); return FAIL; } - expr_start = skipwhite(expr_start + 1); - - expr_end = expr_start; - evalarg_T evalarg; - fill_evalarg_from_eap(&evalarg, eap, FALSE); - skip_expr(&expr_end, NULL); + init_arg = skipwhite(init_arg + 1); - if (type == NULL) - { - // No type specified, use the type of the initializer. - typval_T tv; - tv.v_type = VAR_UNKNOWN; - char_u *expr = expr_start; - int res = eval0(expr, &tv, eap, &evalarg); + fill_evalarg_from_eap(&evalarg, eap, FALSE); + (void)skip_expr_concatenate(&init_arg, &expr_start, &expr_end, &evalarg); - if (res == OK) - { - type = typval2type(&tv, get_copyID(), type_list, - TVTT_DO_MEMBER); - clear_tv(&tv); - } - if (type == NULL) - { - semsg(_(e_cannot_get_object_member_type_from_initializer_str), - expr_start); - clear_evalarg(&evalarg, NULL); - return FAIL; - } - } + // No type specified for the member. Set it to "any" and the correct type will be + // set when the object is instantiated. + if (type == NULL) + type = &t_any; + + *init_expr = vim_strnsave(expr_start, expr_end - expr_start); + // Free the memory pointed by expr_start. clear_evalarg(&evalarg, NULL); } - if (!valid_declaration_type(type)) + else if (!valid_declaration_type(type)) return FAIL; *type_ret = type; - if (expr_end > expr_start) - { - if (init_expr == NULL) - { - emsg(_(e_cannot_initialize_member_in_interface)); - return FAIL; - } - *init_expr = vim_strnsave(expr_start, expr_end - expr_start); - } return OK; } @@ -1740,9 +1722,13 @@ inside_class(cctx_T *cctx_arg, class_T * void copy_object(typval_T *from, typval_T *to) { - *to = *from; - if (to->vval.v_object != NULL) + if (from->vval.v_object == NULL) + to->vval.v_object = NULL; + else + { + to->vval.v_object = from->vval.v_object; ++to->vval.v_object->obj_refcount; + } } /* @@ -1787,9 +1773,13 @@ object_unref(object_T *obj) void copy_class(typval_T *from, typval_T *to) { - *to = *from; - if (to->vval.v_class != NULL) + if (from->vval.v_class == NULL) + to->vval.v_class = NULL; + else + { + to->vval.v_class = from->vval.v_class; ++to->vval.v_class->class_refcount; + } } /* diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -3150,6 +3150,19 @@ compile_def_function( semsg(_(e_trailing_characters_str), expr); goto erret; } + + type_T *type = get_type_on_stack(&cctx, 0); + 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. + where_T where = WHERE_INIT; + where.wt_kind = WT_MEMBER; + where.wt_func_name = (char *)m->ocm_name; + if (need_type_where(type, m->ocm_type, FALSE, -1, + where, &cctx, FALSE, FALSE) == FAIL) + goto erret; + } } else push_default_value(&cctx, m->ocm_type->tt_type,