]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.1780: Vim9 type not defined during object creation v9.0.1780
authorYegappan Lakshmanan <yegappan@yahoo.com>
Tue, 22 Aug 2023 19:29:28 +0000 (21:29 +0200)
committerChristian Brabandt <cb@256bit.org>
Tue, 22 Aug 2023 19:29:28 +0000 (21:29 +0200)
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>
runtime/doc/vim9class.txt
src/testdir/test_vim9_class.vim
src/version.c
src/vim9class.c
src/vim9compile.c

index a68b97c87b991ee152ee5ae4a74a2dc2d76edf46..c68288a0c5d5f9e6b6165ab93d4b5a48f88ea84e 100644 (file)
@@ -423,6 +423,12 @@ Each member and function name can be used only once.  It is not possible to
 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*
index dbd4c3bcbec2f4e76fde7fce0b0b4a9775958f92..7e33c6d448282423136d256bdcfd474c83c48869 100644 (file)
@@ -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
index 3ca3167cb9fcc29ac636313dcf2733d0fdbc2416..7b0615208d0933f7cb52849c587d39e8454a1b0c 100644 (file)
@@ -699,6 +699,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1780,
 /**/
     1779,
 /**/
index e4130459b25df211ec41445bbddbd18f7bac633f..00b1f7d98fa19c22c6627e6bf883990b0be36824 100644 (file)
@@ -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);
+       init_arg = skipwhite(init_arg + 1);
 
-       expr_end = expr_start;
-       evalarg_T evalarg;
        fill_evalarg_from_eap(&evalarg, eap, FALSE);
-       skip_expr(&expr_end, NULL);
+       (void)skip_expr_concatenate(&init_arg, &expr_start, &expr_end, &evalarg);
 
+       // 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)
-       {
-           // 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);
+           type = &t_any;
 
-           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;
-           }
-       }
+       *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 *cl)
     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;
+    }
 }
 
 /*
index 028b0ca152d536f9602cc5cc3a48b6594cca7164..09b4bf143b6487e819a344da1891cf0e31707195 100644 (file)
@@ -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,