changeset 32960:d5c05e15cf81 v9.0.1780

patch 9.0.1780: Vim9 type not defined during object creation Commit: https://github.com/vim/vim/commit/618e47d1cd93954bad26d47e5353b4f1432daa5e Author: Yegappan Lakshmanan <yegappan@yahoo.com> 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 <cb@256bit.org> Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com> Co-authored-by: LemonBoy <thatlemon@gmail.com>
author Christian Brabandt <cb@256bit.org>
date Tue, 22 Aug 2023 21:45:02 +0200
parents a0266fbc89f5
children db0e12cf833e
files runtime/doc/vim9class.txt src/testdir/test_vim9_class.vim src/version.c src/vim9class.c src/vim9compile.c
diffstat 5 files changed, 150 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- 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*
--- 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<C> = { 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
--- 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,
--- 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;
+    }
 }
 
 /*
--- 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,