changeset 33913:a259471e74fe v9.0.2156

patch 9.0.2156: Vim9: can use typealias in assignment Commit: https://github.com/vim/vim/commit/9ed53752df1020a6881ac68d1bde2852c9a680aa Author: Ernie Rael <errael@raelity.com> Date: Mon Dec 11 17:40:46 2023 +0100 patch 9.0.2156: Vim9: can use typealias in assignment Problem: Vim9: can use typealias in an assignment Solution: Generate errors when class/typealias involved in the rhs of an assignment closes: #13637 Signed-off-by: Ernie Rael <errael@raelity.com> Signed-off-by: Christian Brabandt <cb@256bit.org> Generate errors when class/typealias involved in assignment.
author Christian Brabandt <cb@256bit.org>
date Mon, 11 Dec 2023 17:45:06 +0100
parents bc834a974df8
children 966a1af141b0
files src/errors.h src/evalvars.c src/proto/vim9type.pro src/testdir/test_vim9_assign.vim src/testdir/test_vim9_class.vim src/testdir/test_vim9_typealias.vim src/version.c src/vim9compile.c src/vim9execute.c src/vim9type.c
diffstat 10 files changed, 162 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/src/errors.h
+++ b/src/errors.h
@@ -3557,10 +3557,16 @@ EXTERN char e_using_typealias_as_float[]
 	INIT(= N_("E1401: Using type alias \"%s\" as a Float"));
 EXTERN char e_using_typealias_as_string[]
 	INIT(= N_("E1402: Using type alias \"%s\" as a String"));
-EXTERN char e_using_typealias_as_value[]
+EXTERN char e_using_typealias_as_value_str[]
 	INIT(= N_("E1403: Type alias \"%s\" cannot be used as a value"));
 EXTERN char e_abstract_cannot_be_used_in_interface[]
 	INIT(= N_("E1404: Abstract cannot be used in an interface"));
+EXTERN char e_using_class_as_value_str[]
+	INIT(= N_("E1403: Class \"%s\" cannot be used as a value"));
+EXTERN char e_using_class_as_var_val[]
+	INIT(= N_("E1405: Cannot use a Class as a variable or value"));
+EXTERN char e_using_typealias_as_var_val[]
+	INIT(= N_("E1406: Cannot use a Typealias as a variable or value"));
 #endif
 // E1405 - E1499 unused (reserved for Vim9 class support)
 EXTERN char e_cannot_mix_positional_and_non_positional_str[]
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -1836,11 +1836,8 @@ ex_let_one(
 	return NULL;
     }
 
-    if (tv->v_type == VAR_TYPEALIAS)
-    {
-	semsg(_(e_using_typealias_as_value), tv->vval.v_typealias->ta_name);
+    if (check_typval_is_value(tv) == FAIL)
 	return NULL;
-    }
 
     if (*arg == '$')
     {
--- a/src/proto/vim9type.pro
+++ b/src/proto/vim9type.pro
@@ -35,4 +35,7 @@ type_T *get_member_type_from_stack(int c
 char *vartype_name(vartype_T type);
 char *type_name(type_T *type, char **tofree);
 void f_typename(typval_T *argvars, typval_T *rettv);
+int check_vartype_is_value(vartype_T typ);
+int check_typval_is_value(typval_T *tv);
+int check_type_is_value(type_T *type);
 /* vim: set ft=c : */
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -3057,6 +3057,18 @@ def Test_dict_item_assign()
   v9.CheckSourceSuccess(lines)
 enddef
 
+def Test_class_assign()
+  var lines =<< trim END
+    vim9script
+    class C
+    endclass
+    class D
+    endclass
+    assert_fails('C = D', 'E1403: Class "D" cannot be used as a value')
+  END
+  v9.CheckSourceSuccess(lines)
+enddef
+
 " Test for using various types (dict, list, blob, funcref, class) as variable
 " in assignments with a different type
 def Test_type_check()
@@ -3093,7 +3105,7 @@ def Test_type_check()
     assert_fails('N = l', 'E1012: Type mismatch; expected number but got list<number>')
     assert_fails('N = b', 'E1012: Type mismatch; expected number but got blob')
     assert_fails('N = Fn', 'E1012: Type mismatch; expected number but got func([unknown]): number')
-    assert_fails('N = A', 'E1012: Type mismatch; expected number but got class<A>')
+    assert_fails('N = A', 'E1403: Class "A" cannot be used as a value')
     assert_fails('N = o', 'E1012: Type mismatch; expected number but got object<A>')
 
     # Use a compound operator with different RHS types
@@ -3101,7 +3113,7 @@ def Test_type_check()
     assert_fails('N += l', 'E734: Wrong variable type for +=')
     assert_fails('N += b', 'E974: Using a Blob as a Number')
     assert_fails('N += Fn', 'E734: Wrong variable type for +=')
-    assert_fails('N += A', 'E1319: Using a Class as a Number')
+    assert_fails('N += A', 'E1403: Class "A" cannot be used as a value')
     assert_fails('N += o', 'E1320: Using an Object as a Number')
 
     # Initialize multiple variables using []
@@ -3109,7 +3121,7 @@ def Test_type_check()
     assert_fails('var [X2: number, Y: number] = [1, l]', 'E1012: Type mismatch; expected number but got list<number>')
     assert_fails('var [X3: number, Y: number] = [1, b]', 'E1012: Type mismatch; expected number but got blob')
     assert_fails('var [X4: number, Y: number] = [1, Fn]', 'E1012: Type mismatch; expected number but got func([unknown]): number')
-    assert_fails('var [X7: number, Y: number] = [1, A]', 'E1012: Type mismatch; expected number but got class<A>')
+    assert_fails('var [X7: number, Y: number] = [1, A]', 'E1403: Class "A" cannot be used as a value')
     assert_fails('var [X8: number, Y: number] = [1, o]', 'E1012: Type mismatch; expected number but got object<A>')
 
     # String concatenation with various LHS types
@@ -3117,7 +3129,7 @@ def Test_type_check()
     assert_fails('S ..= l', 'E734: Wrong variable type for .=')
     assert_fails('S ..= b', 'E976: Using a Blob as a String')
     assert_fails('S ..= Fn', 'E734: Wrong variable type for .=')
-    assert_fails('S ..= A', 'E1323: Using a Class as a String')
+    assert_fails('S ..= A', 'E1403: Class "A" cannot be used as a value')
     assert_fails('S ..= o', 'E1324: Using an Object as a String')
 
     # String concatenation with various RHS types
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -3093,25 +3093,77 @@ def Test_closure_in_class()
   v9.CheckSourceSuccess(lines)
 enddef
 
-def Test_call_constructor_from_legacy()
+def Test_construct_object_from_legacy()
+  # Cannot directly invoke constructor from legacy
   var lines =<< trim END
     vim9script
 
-    var newCalled = 'false'
-
-    class A
+    var newCalled = false
+
+    class A
+      def new(arg: string)
+        newCalled = true
+      enddef
+    endclass
+
+    export def CreateA(...args: list<any>): A
+      return call(A.new, args)
+    enddef
+
+    g:P = CreateA
+    legacy call g:P('some_arg')
+    assert_equal(true, newCalled)
+    unlet g:P
+  END
+  v9.CheckSourceSuccess(lines)
+
+  lines =<< trim END
+    vim9script
+
+    var newCalled = false
+
+    class A
+      static def CreateA(options = {}): any
+        return A.new()
+      enddef
       def new()
-        newCalled = 'true'
-      enddef
-    endclass
-
-    export def F(options = {}): any
-      return A
-    enddef
-
-    g:p = F()
-    legacy call p.new()
-    assert_equal('true', newCalled)
+        newCalled = true
+      enddef
+    endclass
+
+    g:P = A.CreateA
+    legacy call g:P()
+    assert_equal(true, newCalled)
+    unlet g:P
+  END
+  v9.CheckSourceSuccess(lines)
+
+  # This also tests invoking "new()" with "call"
+  lines =<< trim END
+    vim9script
+
+    var createdObject: any
+
+    class A
+      this.val1: number
+      this.val2: number
+      static def CreateA(...args: list<any>): any
+        createdObject = call(A.new, args)
+        return createdObject
+      enddef
+    endclass
+
+    g:P = A.CreateA
+    legacy call g:P(3, 5)
+    assert_equal(3, createdObject.val1)
+    assert_equal(5, createdObject.val2)
+    legacy call g:P()
+    assert_equal(0, createdObject.val1)
+    assert_equal(0, createdObject.val2)
+    legacy call g:P(7)
+    assert_equal(7, createdObject.val1)
+    assert_equal(0, createdObject.val2)
+    unlet g:P
   END
   v9.CheckSourceSuccess(lines)
 enddef
--- a/src/testdir/test_vim9_typealias.vim
+++ b/src/testdir/test_vim9_typealias.vim
@@ -212,7 +212,7 @@ def Test_typealias()
     enddef
     Foo()
   END
-  v9.CheckSourceFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 1)
+  v9.CheckSourceFailure(lines, 'E1406: Cannot use a Typealias as a variable or value', 1)
 
   # Using type alias in an expression (script level)
   lines =<< trim END
--- 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 */
 /**/
+    2156,
+/**/
     2155,
 /**/
     2154,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2797,6 +2797,8 @@ compile_assignment(
 
 		rhs_type = cctx->ctx_type_stack.ga_len == 0 ? &t_void
 						  : get_type_on_stack(cctx, 0);
+		if (check_type_is_value(rhs_type) == FAIL)
+		    goto theend;
 		if (lhs.lhs_lvar != NULL && (is_decl || !lhs.lhs_has_type))
 		{
 		    if ((rhs_type->tt_type == VAR_FUNC
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -3813,10 +3813,8 @@ exec_instructions(ectx_T *ectx)
 	    case ISN_STORE:
 		--ectx->ec_stack.ga_len;
 		tv = STACK_TV_VAR(iptr->isn_arg.number);
-		if (STACK_TV_BOT(0)->v_type == VAR_TYPEALIAS)
+		if (check_typval_is_value(STACK_TV_BOT(0)) == FAIL)
 		{
-		    semsg(_(e_using_typealias_as_value),
-				STACK_TV_BOT(0)->vval.v_typealias->ta_name);
 		    clear_tv(STACK_TV_BOT(0));
 		    goto on_error;
 		}
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -1846,4 +1846,66 @@ f_typename(typval_T *argvars, typval_T *
     clear_type_list(&type_list);
 }
 
+/*
+ * Check if the typval_T is a value type; report an error if it is not.
+ * Note: a type, user defined or typealias, is not a value type.
+ *
+ * Return OK if it's a value type, else FAIL
+ */
+    int
+check_typval_is_value(typval_T *tv)
+{
+    if (tv->v_type == VAR_CLASS)
+    {
+        semsg(_(e_using_class_as_value_str), tv->vval.v_class->class_name);
+	return FAIL;
+    }
+    else if (tv->v_type == VAR_TYPEALIAS)
+    {
+        semsg(_(e_using_typealias_as_value_str), tv->vval.v_typealias->ta_name);
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Same as above, except check type_T.
+ */
+    int
+check_type_is_value(type_T *type)
+{
+    if (type->tt_type == VAR_CLASS)
+    {
+        semsg(_(e_using_class_as_value_str), type->tt_class->class_name);
+	return FAIL;
+    }
+    else if (type->tt_type == VAR_TYPEALIAS)
+    {
+	// Not sure what could be done here to get a name
+	// TODO: MAYBE AN OPTIONAL ARGUMENT
+        emsg(_(e_using_typealias_as_var_val));
+	return FAIL;
+    }
+    return OK;
+}
+
+/*
+ * Same as above, except check vartype_T.
+ */
+    int
+check_vartype_is_value(vartype_T typ)
+{
+    if (typ == VAR_CLASS)
+    {
+	emsg(_(e_using_class_as_var_val));
+	return FAIL;
+    }
+    else if (typ == VAR_TYPEALIAS)
+    {
+        emsg(_(e_using_typealias_as_var_val));
+        return FAIL;
+    }
+    return OK;
+}
+
 #endif // FEAT_EVAL