# HG changeset patch # User Bram Moolenaar # Date 1670966103 -3600 # Node ID 9ae3720f9bd93f5fe5071e99256bef5fd9ba8950 # Parent 2a103f2e659b21bcc646c24b3d97063878f39ab4 patch 9.0.1054: object member can't get type from initializer Commit: https://github.com/vim/vim/commit/74e1274edf632b83d2948a2a2d519589eff52997 Author: Bram Moolenaar 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. diff --git a/src/errors.h b/src/errors.h --- 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 diff --git a/src/proto/vim9type.pro b/src/proto/vim9type.pro --- 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); 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 @@ -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 diff --git a/src/version.c b/src/version.c --- 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, diff --git a/src/vim9class.c b/src/vim9class.c --- 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; diff --git a/src/vim9type.c b/src/vim9type.c --- 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. */