changeset 31443:9ae3720f9bd9 v9.0.1054

patch 9.0.1054: object member can't get type from initializer Commit: https://github.com/vim/vim/commit/74e1274edf632b83d2948a2a2d519589eff52997 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Dec 13 21:14:28 2022 +0000 patch 9.0.1054: object member can't get type from initializer Problem: Object member can't get type from initializer. Solution: If there is no type specified try to use the type of the initializer. Check for a valid type.
author Bram Moolenaar <Bram@vim.org>
date Tue, 13 Dec 2022 22:15:03 +0100
parents 2a103f2e659b
children 4bcbc731d283
files src/errors.h src/proto/vim9type.pro src/testdir/test_vim9_class.vim src/version.c src/vim9class.c src/vim9type.c
diffstat 6 files changed, 149 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/src/errors.h
+++ b/src/errors.h
@@ -3374,4 +3374,8 @@ EXTERN char e_object_required_found_str[
 	INIT(= N_("E1327: Object required, found %s"));
 EXTERN char e_constructor_default_value_must_be_vnone_str[]
 	INIT(= N_("E1328: Constructor default value must be v:none: %s"));
-#endif
+EXTERN char e_cannot_get_object_member_type_from_initializer_str[]
+	INIT(= N_("E1329: Cannot get object member type from initializer: %s"));
+EXTERN char e_invalid_type_for_object_member_str[]
+	INIT(= N_("E1330: Invalid type for object member: %s"));
+#endif
--- a/src/proto/vim9type.pro
+++ b/src/proto/vim9type.pro
@@ -13,6 +13,7 @@ int func_type_add_arg_types(type_T *func
 int type_any_or_unknown(type_T *type);
 int need_convert_to_bool(type_T *type, typval_T *tv);
 type_T *typval2type(typval_T *tv, int copyID, garray_T *type_gap, int flags);
+int valid_declaration_type(type_T *type);
 type_T *typval2type_vimvar(typval_T *tv, garray_T *type_gap);
 int check_typval_arg_type(type_T *expected, typval_T *actual_tv, char *func_name, int arg_idx);
 int check_typval_type(type_T *expected, typval_T *actual_tv, where_T where);
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -231,7 +231,58 @@ def Test_class_default_new()
       assert_equal("none", chris.education)
   END
   v9.CheckScriptSuccess(lines)
+
+  lines =<< trim END
+      vim9script
+      class Person
+        this.name: string
+        this.age: number = 42
+        this.education: string = "unknown"
+
+        def new(this.name, this.age = v:none, this.education = v:none)
+        enddef
+      endclass
+
+      var missing = Person.new()
+  END
+  v9.CheckScriptFailure(lines, 'E119:')
+enddef
+
+def Test_class_object_member_inits()
+  var lines =<< trim END
+      vim9script
+      class TextPosition
+        this.lnum: number
+        this.col = 1
+        this.addcol: number = 2
+      endclass
+
+      var pos = TextPosition.new()
+      assert_equal(0, pos.lnum)
+      assert_equal(1, pos.col)
+      assert_equal(2, pos.addcol)
+  END
+  v9.CheckScriptSuccess(lines)
+
+  lines =<< trim END
+      vim9script
+      class TextPosition
+        this.lnum
+        this.col = 1
+      endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1022:')
+
+  lines =<< trim END
+      vim9script
+      class TextPosition
+        this.lnum = v:none
+        this.col = 1
+      endclass
+  END
+  v9.CheckScriptFailure(lines, 'E1330:')
 enddef
 
 
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1054,
+/**/
     1053,
 /**/
     1052,
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -125,43 +125,74 @@ ex_class(exarg_T *eap)
 	    char_u *varname_end = to_name_end(varname, FALSE);
 
 	    char_u *colon = skipwhite(varname_end);
-	    // TODO: accept initialization and figure out type from it
-	    if (*colon != ':')
+	    char_u *type_arg = colon;
+	    type_T *type = NULL;
+	    if (*colon == ':')
+	    {
+		if (VIM_ISWHITE(*varname_end))
+		{
+		    semsg(_(e_no_white_space_allowed_before_colon_str),
+								      varname);
+		    break;
+		}
+		if (!VIM_ISWHITE(colon[1]))
+		{
+		    semsg(_(e_white_space_required_after_str_str), ":",
+								      varname);
+		    break;
+		}
+		type_arg = skipwhite(colon + 1);
+		type = parse_type(&type_arg, &type_list, TRUE);
+		if (type == NULL)
+		    break;
+	    }
+
+	    char_u *expr_start = skipwhite(type_arg);
+	    char_u *expr_end = expr_start;
+	    if (type == NULL && *expr_start != '=')
 	    {
 		emsg(_(e_type_or_initialization_required));
 		break;
 	    }
-	    if (VIM_ISWHITE(*varname_end))
-	    {
-		semsg(_(e_no_white_space_allowed_before_colon_str), varname);
-		break;
-	    }
-	    if (!VIM_ISWHITE(colon[1]))
+
+	    if (*expr_start == '=')
 	    {
-		semsg(_(e_white_space_required_after_str_str), ":", varname);
-		break;
-	    }
+		if (!VIM_ISWHITE(expr_start[-1]) || !VIM_ISWHITE(expr_start[1]))
+		{
+		    semsg(_(e_white_space_required_before_and_after_str_at_str),
+								"=", type_arg);
+		    break;
+		}
+		expr_start = skipwhite(expr_start + 1);
 
-	    char_u *type_arg = skipwhite(colon + 1);
-	    type_T *type = parse_type(&type_arg, &type_list, TRUE);
-	    if (type == NULL)
-		break;
+		expr_end = expr_start;
+		evalarg_T evalarg;
+		fill_evalarg_from_eap(&evalarg, eap, FALSE);
+		skip_expr(&expr_end, &evalarg);
 
-	    char_u *expr_start = skipwhite(type_arg);
-	    if (*expr_start == '=' && (!VIM_ISWHITE(expr_start[-1])
-					       || !VIM_ISWHITE(expr_start[1])))
-	    {
-		semsg(_(e_white_space_required_before_and_after_str_at_str),
-								"=", type_arg);
-		break;
+		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);
+
+		    if (res == OK)
+			type = typval2type(&tv, get_copyID(), &type_list,
+							       TVTT_DO_MEMBER);
+		    if (type == NULL)
+		    {
+			semsg(_(e_cannot_get_object_member_type_from_initializer_str),
+				expr_start);
+			clear_evalarg(&evalarg, NULL);
+			break;
+		    }
+		}
+		clear_evalarg(&evalarg, NULL);
 	    }
-	    expr_start = skipwhite(expr_start + 1);
-
-	    char_u *expr_end = expr_start;
-	    evalarg_T	evalarg;
-	    init_evalarg(&evalarg);
-	    skip_expr(&expr_end, &evalarg);
-	    clear_evalarg(&evalarg, NULL);
+	    if (!valid_declaration_type(type))
+		break;
 
 	    if (ga_grow(&objmembers, 1) == FAIL)
 		break;
--- a/src/vim9type.c
+++ b/src/vim9type.c
@@ -425,6 +425,17 @@ typval2type_int(typval_T *tv, int copyID
 	return &t_number;
     if (tv->v_type == VAR_BOOL)
 	return &t_bool;
+    if (tv->v_type == VAR_SPECIAL)
+    {
+	if (tv->vval.v_number == VVAL_NULL)
+	    return &t_null;
+	if (tv->vval.v_number == VVAL_NONE)
+	    return &t_none;
+	if (tv->vval.v_number == VVAL_TRUE
+		|| tv->vval.v_number == VVAL_TRUE)
+	    return &t_bool;
+	return &t_unknown;
+    }
     if (tv->v_type == VAR_STRING)
 	return &t_string;
     if (tv->v_type == VAR_BLOB)
@@ -620,6 +631,25 @@ typval2type(typval_T *tv, int copyID, ga
 }
 
 /*
+ * Return TRUE if "type" can be used for a variable declaration.
+ * Give an error and return FALSE if not.
+ */
+    int
+valid_declaration_type(type_T *type)
+{
+    if (type->tt_type == VAR_SPECIAL  // null, none
+	    || type->tt_type == VAR_VOID)
+    {
+	char *tofree = NULL;
+	char *name = type_name(type, &tofree);
+	semsg(_(e_invalid_type_for_object_member_str), name);
+	vim_free(tofree);
+	return FALSE;
+    }
+    return TRUE;
+}
+
+/*
  * Get a type_T for a typval_T, used for v: variables.
  * "type_list" is used to temporarily create types in.
  */