# HG changeset patch # User Christian Brabandt # Date 1698501606 -7200 # Node ID f126ffc85f7c34b32434770b9b67931161fb158f # Parent 9d9f6d05091d6d788b7e2928003c6b187c78bf62 patch 9.0.2078: several problems with type aliases Commit: https://github.com/vim/vim/commit/feaccd239573a6265d39d3a917862ee40742eab4 Author: Yegappan Lakshmanan Date: Sat Oct 28 15:53:55 2023 +0200 patch 9.0.2078: several problems with type aliases Problem: several problems with type aliases Solution: Check for more error conditions, add tests, fix issues Check for more error conditions and add additional tests fixes #13434 fixes #13437 fixes #13438 closes #13441 Signed-off-by: Yegappan Lakshmanan Signed-off-by: Christian Brabandt diff --git a/runtime/doc/vim9class.txt b/runtime/doc/vim9class.txt --- a/runtime/doc/vim9class.txt +++ b/runtime/doc/vim9class.txt @@ -749,7 +749,9 @@ type alias. For Example: > :type ListOfStrings = list The type alias can be used wherever a built-in type can be used. The type -alias name must start with an upper case character. +alias name must start with an upper case character. A type alias can be +created only at the script level and not inside a function. A type alias can +be exported and used across scripts. ============================================================================== diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3542,8 +3542,8 @@ EXTERN char e_type_can_only_be_defined_i INIT(= N_("E1393: Type can only be defined in Vim9 script")); EXTERN char e_type_name_must_start_with_uppercase_letter_str[] INIT(= N_("E1394: Type name must start with an uppercase letter: %s")); -EXTERN char e_using_typealias_as_variable[] - INIT(= N_("E1395: Type alias \"%s\" cannot be used as a variable")); +EXTERN char e_cannot_modify_typealias[] + INIT(= N_("E1395: Type alias \"%s\" cannot be modified")); EXTERN char e_typealias_already_exists_for_str[] INIT(= N_("E1396: Type alias \"%s\" already exists")); EXTERN char e_missing_typealias_name[] @@ -3552,8 +3552,16 @@ EXTERN char e_missing_typealias_type[] INIT(= N_("E1398: Missing type alias type")); EXTERN char e_type_can_only_be_used_in_script[] INIT(= N_("E1399: Type can only be used in a script")); -#endif -// E1400 - E1499 unused (reserved for Vim9 class support) +EXTERN char e_using_typealias_as_number[] + INIT(= N_("E1400: Using type alias \"%s\" as a Number")); +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[] + INIT(= N_("E1403: Type alias \"%s\" cannot be used as a value")); +#endif +// E1404 - E1499 unused (reserved for Vim9 class support) EXTERN char e_cannot_mix_positional_and_non_positional_str[] INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s")); EXTERN char e_fmt_arg_nr_unused_str[] diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1885,7 +1885,7 @@ set_var_lval( { if (di != NULL && di->di_tv.v_type == VAR_TYPEALIAS) { - semsg(_(e_using_typealias_as_variable), + semsg(_(e_cannot_modify_typealias), di->di_tv.vval.v_typealias->ta_name); clear_tv(&tv); return; @@ -4045,7 +4045,8 @@ eval8( if (!equal_type(want_type, actual, 0)) { - if (want_type == &t_bool && actual != &t_bool + if (want_type->tt_type == VAR_BOOL + && actual->tt_type != VAR_BOOL && (actual->tt_flags & TTFLAG_BOOL_OK)) { int n = tv2bool(rettv); diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -1838,7 +1838,7 @@ ex_let_one( if (tv->v_type == VAR_TYPEALIAS) { - semsg(_(e_using_typealias_as_variable), tv->vval.v_typealias->ta_name); + semsg(_(e_using_typealias_as_value), tv->vval.v_typealias->ta_name); return NULL; } @@ -3979,7 +3979,7 @@ set_var_const( if (di->di_tv.v_type == VAR_TYPEALIAS) { - semsg(_(e_using_typealias_as_variable), + semsg(_(e_cannot_modify_typealias), di->di_tv.vval.v_typealias->ta_name); clear_tv(&di->di_tv); goto failed; diff --git a/src/proto/typval.pro b/src/proto/typval.pro --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -52,6 +52,7 @@ int check_for_list_or_dict_or_blob_arg(t int check_for_list_or_dict_or_blob_or_string_arg(typval_T *args, int idx); int check_for_opt_buffer_or_dict_arg(typval_T *args, int idx); int check_for_object_arg(typval_T *args, int idx); +int tv_class_alias(typval_T *tv); int check_for_class_or_list_arg(typval_T *args, int idx); char_u *tv_get_string(typval_T *varp); char_u *tv_get_string_strict(typval_T *varp); diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -4785,16 +4785,15 @@ enddef " Test for :type command to create type aliases def Test_typealias() + # Use type alias at script level var lines =<< trim END vim9script type ListOfStrings = list - var a: ListOfStrings = ['a', 'b'] - assert_equal(['a', 'b'], a) - def Foo(b: ListOfStrings): ListOfStrings - var c: ListOfStrings = ['c', 'd'] - assert_equal(['c', 'd'], c) - return b + def Foo(a: ListOfStrings): ListOfStrings + return a enddef + var b: ListOfStrings = ['a', 'b'] + assert_equal(['a', 'b'], b) assert_equal(['e', 'f'], Foo(['e', 'f'])) assert_equal('typealias>', typename(ListOfStrings)) assert_equal(v:t_typealias, type(ListOfStrings)) @@ -4803,6 +4802,26 @@ def Test_typealias() END v9.CheckSourceSuccess(lines) + # Use type alias at def function level + lines =<< trim END + vim9script + type ListOfStrings = list + def Foo(a: ListOfStrings): ListOfStrings + return a + enddef + def Bar() + var c: ListOfStrings = ['c', 'd'] + assert_equal(['c', 'd'], c) + assert_equal(['e', 'f'], Foo(['e', 'f'])) + assert_equal('typealias>', typename(ListOfStrings)) + assert_equal(v:t_typealias, type(ListOfStrings)) + assert_equal('ListOfStrings', string(ListOfStrings)) + assert_equal(false, null == ListOfStrings) + enddef + Bar() + END + v9.CheckSourceSuccess(lines) + # Use :type outside a Vim9 script lines =<< trim END type Index = number @@ -4861,9 +4880,9 @@ def Test_typealias() # type alias starting with lower-case letter lines =<< trim END vim9script - type index number - END - v9.CheckSourceFailure(lines, 'E1394: Type name must start with an uppercase letter: index number', 2) + type index = number + END + v9.CheckSourceFailure(lines, 'E1394: Type name must start with an uppercase letter: index = number', 2) # No white space following the alias name lines =<< trim END @@ -4895,27 +4914,74 @@ def Test_typealias() END v9.CheckSourceFailure(lines, 'E1396: Type alias "MyList" already exists', 3) - # Sourcing a script twice (which will free script local variables) + # def function argument name collision with a type alias + lines =<< trim END + vim9script + type A = list + def Foo(A: number) + enddef + END + v9.CheckSourceFailure(lines, 'E1168: Argument already declared in the script: A: number)', 3) + + # def function local variable name collision with a type alias + lines =<< trim END + vim9script + type A = list + def Foo() + var A: number = 10 + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1054: Variable already declared in the script: A', 1) + + # type alias a variable + lines =<< trim END + vim9script + var A: list = [] + type B = A + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: A', 3) + + # type alias a class lines =<< trim END vim9script class C endclass type AC = C - assert_equal('typealias>', typename(AC)) - END + assert_equal('class', typename(AC)) + END + v9.CheckSourceSuccess(lines) + + # Sourcing a script twice (which will free script local variables) + # Uses "lines" from the previous test new setline(1, lines) :source :source bw! + # type alias a type alias + lines =<< trim END + vim9script + type A = string + type B = A + var b: B = 'abc' + assert_equal('abc', b) + def Foo() + var c: B = 'def' + assert_equal('def', c) + enddef + Foo() + END + v9.CheckSourceSuccess(lines) + # Assigning to a type alias (script level) lines =<< trim END vim9script type MyType = list MyType = [1, 2, 3] END - v9.CheckSourceFailure(lines, 'E1395: Type alias "MyType" cannot be used as a variable', 3) + v9.CheckSourceFailure(lines, 'E1395: Type alias "MyType" cannot be modified', 3) # Assigning a type alias (def function level) lines =<< trim END @@ -4926,16 +4992,18 @@ def Test_typealias() enddef Foo() END - v9.CheckSourceFailure(lines, 'E1395: Type alias "A" cannot be used as a variable', 1) + v9.CheckSourceFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 1) # Using type alias in an expression (script level) lines =<< trim END vim9script type MyType = list - assert_fails('var m = MyType', 'E1395: Type alias "MyType" cannot be used as a variable') - assert_fails('var i = MyType + 1', 'E1395: Type alias "MyType" cannot be used as a variable') - assert_fails('var f = 1.0 + MyType', 'E1395: Type alias "MyType" cannot be used as a variable') - assert_fails('MyType += 10', 'E1395: Type alias "MyType" cannot be used as a variable') + assert_fails('var m = MyType', 'E1403: Type alias "MyType" cannot be used as a value') + assert_fails('var i = MyType + 1', 'E1400: Using type alias "MyType" as a Number') + assert_fails('var f = 1.0 + MyType', 'E1400: Using type alias "MyType" as a Number') + assert_fails('MyType += 10', 'E1395: Type alias "MyType" cannot be modified') + assert_fails('var x = $"-{MyType}-"', 'E1402: Using type alias "MyType" as a String') + assert_fails('var x = MyType[1]', 'E909: Cannot index a special variable') END v9.CheckSourceSuccess(lines) @@ -4948,7 +5016,7 @@ def Test_typealias() enddef Foo() END - v9.CheckSourceFailure(lines, 'E1395: Type alias "MyType" cannot be used as a variable', 1) + v9.CheckSourceFailure(lines, 'E1051: Wrong argument type for +', 1) # Using type alias in an expression (def function level) lines =<< trim END @@ -4972,6 +5040,25 @@ def Test_typealias() END v9.CheckSourceFailure(lines, 'E46: Cannot change read-only variable "MyType"', 1) + # Convert type alias to a string (def function level) + lines =<< trim END + vim9script + type MyType = list + def Foo() + var x = $"-{MyType}-" + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1105: Cannot convert typealias to string', 1) + + # Using type alias as a float + lines =<< trim END + vim9script + type B = number + sort([1.1, B], 'f') + END + v9.CheckSourceFailure(lines, 'E1401: Using type alias "B" as a Float', 3) + # Creating a typealias in a def function lines =<< trim END vim9script @@ -5010,11 +5097,19 @@ def Test_typealias() enddef Foo() END - v9.CheckSourceFailure(lines, 'E1395: Type alias "A" cannot be used as a variable', 2) + v9.CheckSourceFailure(lines, 'E1072: Cannot compare typealias with number', 2) + + # casting a number to a type alias (script level) + lines =<< trim END + vim9script + type MyType = bool + assert_equal(true, 1 == true) + END + v9.CheckSourceSuccess(lines) enddef " Test for exporting and importing type aliases -def Test_import_typealias() +def Test_typealias_import() var lines =<< trim END vim9script export type MyType = list @@ -5073,6 +5168,24 @@ def Test_import_typealias() assert_equal([4, 5, 6], myList2) END v9.CheckScriptSuccess(lines) + + # Using an exported class to create a type alias + lines =<< trim END + vim9script + export class MyClass + this.val = 10 + endclass + END + writefile(lines, 'Xtypeexport4.vim', 'D') + lines =<< trim END + vim9script + import './Xtypeexport4.vim' as T + + type MyType3 = T.MyClass + var c: MyType3 = T.MyClass.new() + assert_equal(10, c.val) + END + v9.CheckScriptSuccess(lines) enddef " Test for using typealias as a def function argument and return type @@ -5131,6 +5244,17 @@ def Test_typealias_with_builtin_function END v9.CheckScriptFailure(lines, 'E701: Invalid type for len()', 3) + # Using a type alias with len() + lines =<< trim END + vim9script + type A = list + def Foo() + var x = len(A) + enddef + Foo() + END + v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected list but got typealias', 1) + # Using a type alias with eval() lines =<< trim END vim9script @@ -5140,7 +5264,7 @@ def Test_typealias_with_builtin_function enddef Foo() END - v9.CheckScriptFailure(lines, 'E1395: Type alias "A" cannot be used as a variable', 1) + v9.CheckScriptFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 1) enddef " Test for type alias refcount @@ -5161,6 +5285,37 @@ def Test_typealias_refcount() v9.CheckScriptSuccess(lines) enddef +" Test for using instanceof() with a type alias +def Test_typealias_instanceof() + var lines =<< trim END + vim9script + class C + endclass + + type Ctype = C + var o = C.new() + assert_equal(1, instanceof(o, Ctype)) + type Ntype = number + assert_fails('instanceof(o, Ntype)', 'E693: List or Class required for argument 2') + assert_equal(1, instanceof(o, [Ctype])) + END + v9.CheckScriptSuccess(lines) +enddef + +" Test for type aliasing a class +def Test_typealias_class() + var lines =<< trim END + vim9script + class C + this.color = 'green' + endclass + type MyClass = C + var o: MyClass = MyClass.new() + assert_equal('green', o.color) + END + v9.CheckScriptSuccess(lines) +enddef + " Keep this last, it messes up highlighting. def Test_substitute_cmd() new diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -271,7 +271,7 @@ tv_get_bool_or_number_chk( emsg(_(e_cannot_use_void_value)); break; case VAR_TYPEALIAS: - semsg(_(e_using_typealias_as_variable), + semsg(_(e_using_typealias_as_number), varp->vval.v_typealias->ta_name); break; case VAR_UNKNOWN: @@ -392,7 +392,7 @@ tv_get_float_chk(typval_T *varp, int *er emsg(_(e_cannot_use_void_value)); break; case VAR_TYPEALIAS: - semsg(_(e_using_typealias_as_variable), + semsg(_(e_using_typealias_as_float), varp->vval.v_typealias->ta_name); break; case VAR_UNKNOWN: @@ -1004,12 +1004,23 @@ check_for_object_arg(typval_T *args, int } /* + * Returns TRUE if "tv" is a type alias for a class + */ + int +tv_class_alias(typval_T *tv) +{ + return tv->v_type == VAR_TYPEALIAS && + tv->vval.v_typealias->ta_type->tt_type == VAR_OBJECT; +} + +/* * Give an error and return FAIL unless "args[idx]" is a class or a list. */ int check_for_class_or_list_arg(typval_T *args, int idx) { - if (args[idx].v_type != VAR_CLASS && args[idx].v_type != VAR_LIST) + if (args[idx].v_type != VAR_CLASS && args[idx].v_type != VAR_LIST + && !tv_class_alias(&args[idx])) { semsg(_(e_list_or_class_required_for_argument_nr), idx + 1); return FAIL; @@ -1146,6 +1157,9 @@ tv_get_string_buf_chk_strict(typval_T *v emsg(_(e_cannot_use_void_value)); break; case VAR_TYPEALIAS: + semsg(_(e_using_typealias_as_string), + varp->vval.v_typealias->ta_name); + break; case VAR_UNKNOWN: case VAR_ANY: case VAR_INSTR: diff --git a/src/version.c b/src/version.c --- 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 */ /**/ + 2078, +/**/ 2077, /**/ 2076, diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -2196,13 +2196,26 @@ ex_type(exarg_T *eap UNUSED) goto done; } - // Add the user-defined type to the script-local variables. - tv.v_type = VAR_TYPEALIAS; - tv.v_lock = 0; - tv.vval.v_typealias = ALLOC_CLEAR_ONE(typealias_T); - ++tv.vval.v_typealias->ta_refcount; - tv.vval.v_typealias->ta_name = vim_strsave(name_start); - tv.vval.v_typealias->ta_type = type; + // Create a script-local variable for the type alias. + if (type->tt_type != VAR_OBJECT) + { + tv.v_type = VAR_TYPEALIAS; + tv.v_lock = 0; + tv.vval.v_typealias = ALLOC_CLEAR_ONE(typealias_T); + ++tv.vval.v_typealias->ta_refcount; + tv.vval.v_typealias->ta_name = vim_strsave(name_start); + tv.vval.v_typealias->ta_type = type; + } + else + { + // When creating a type alias for a class, use the class type itself to + // create the type alias variable. This is needed to use the type + // alias to invoke class methods (e.g. new()) and use class variables. + tv.v_type = VAR_CLASS; + tv.v_lock = 0; + tv.vval.v_class = type->tt_class; + ++tv.vval.v_class->class_refcount; + } set_var_const(name_start, current_sctx.sc_sid, NULL, &tv, FALSE, ASSIGN_CONST | ASSIGN_FINAL, 0); @@ -3155,6 +3168,7 @@ f_instanceof(typval_T *argvars, typval_T typval_T *object_tv = &argvars[0]; typval_T *classinfo_tv = &argvars[1]; listitem_T *li; + class_T *c; rettv->vval.v_number = VVAL_FALSE; @@ -3169,25 +3183,35 @@ f_instanceof(typval_T *argvars, typval_T { FOR_ALL_LIST_ITEMS(classinfo_tv->vval.v_list, li) { - if (li->li_tv.v_type != VAR_CLASS) + if (li->li_tv.v_type != VAR_CLASS && !tv_class_alias(&li->li_tv)) { emsg(_(e_class_required)); return; } - if (class_instance_of(object_tv->vval.v_object->obj_class, - li->li_tv.vval.v_class) == TRUE) + if (li->li_tv.v_type == VAR_TYPEALIAS) + c = li->li_tv.vval.v_typealias->ta_type->tt_class; + else + c = li->li_tv.vval.v_class; + + if (class_instance_of(object_tv->vval.v_object->obj_class, c) + == TRUE) { rettv->vval.v_number = VVAL_TRUE; return; } } + + return; } - else if (classinfo_tv->v_type == VAR_CLASS) - { - rettv->vval.v_number = class_instance_of(object_tv->vval.v_object->obj_class, - classinfo_tv->vval.v_class); - } + + if (classinfo_tv->v_type == VAR_TYPEALIAS) + c = classinfo_tv->vval.v_typealias->ta_type->tt_class; + else + c = classinfo_tv->vval.v_class; + + rettv->vval.v_number = + class_instance_of(object_tv->vval.v_object->obj_class, c); } #endif // FEAT_EVAL diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -3809,7 +3809,7 @@ exec_instructions(ectx_T *ectx) tv = STACK_TV_VAR(iptr->isn_arg.number); if (STACK_TV_BOT(0)->v_type == VAR_TYPEALIAS) { - semsg(_(e_using_typealias_as_variable), + semsg(_(e_using_typealias_as_value), STACK_TV_BOT(0)->vval.v_typealias->ta_name); clear_tv(STACK_TV_BOT(0)); goto on_error; diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -530,13 +530,6 @@ compile_load_scriptvar( { svar_T *sv = ((svar_T *)si->sn_var_vals.ga_data) + idx; - if (sv->sv_tv->v_type == VAR_TYPEALIAS) - { - semsg(_(e_using_typealias_as_variable), - sv->sv_tv->vval.v_typealias->ta_name); - return FAIL; - } - generate_VIM9SCRIPT(cctx, ISN_LOADSCRIPT, current_sctx.sc_sid, idx, sv->sv_type); return OK; diff --git a/src/vim9type.c b/src/vim9type.c --- a/src/vim9type.c +++ b/src/vim9type.c @@ -1811,7 +1811,13 @@ f_typename(typval_T *argvars, typval_T * rettv->v_type = VAR_STRING; ga_init2(&type_list, sizeof(type_T *), 10); if (argvars[0].v_type == VAR_TYPEALIAS) - type = argvars[0].vval.v_typealias->ta_type; + { + type = copy_type(argvars[0].vval.v_typealias->ta_type, &type_list); + // A type alias for a class has the type set to VAR_OBJECT. Change it + // to VAR_CLASS, so that the name is "typealias>" + if (type->tt_type == VAR_OBJECT) + type->tt_type = VAR_CLASS; + } else type = typval2type(argvars, get_copyID(), &type_list, TVTT_DO_MEMBER); name = type_name(type, &tofree);