# HG changeset patch # User Christian Brabandt # Date 1698428705 -7200 # Node ID 7d9d2404a3d41c762afab965a03f0ab61031500e # Parent 96debbc2abd9f936d46f6963c68b8a92a365fa84 patch 9.0.2076: Vim9: No support for type aliases Commit: https://github.com/vim/vim/commit/ec3cebbd2b6b7583d2f683f5e66345163ec122aa Author: Yegappan Lakshmanan Date: Fri Oct 27 19:35:26 2023 +0200 patch 9.0.2076: Vim9: No support for type aliases Problem: Vim9: No support for type aliases Solution: Implement :type command A type definition is giving a name to a type specification. This also known type alias. :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. closes: #13407 Signed-off-by: Christian Brabandt Signed-off-by: Yegappan Lakshmanan diff --git a/runtime/doc/vim9class.txt b/runtime/doc/vim9class.txt --- a/runtime/doc/vim9class.txt +++ b/runtime/doc/vim9class.txt @@ -743,12 +743,13 @@ constructor methods. 7. Type definition *Vim9-type* *:type* -{not implemented yet} +A type definition is giving a name to a type specification. This also known +type alias. For Example: > -A type definition is giving a name to a type specification. For Example: > + :type ListOfStrings = list - :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. ============================================================================== diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3384,17 +3384,17 @@ EXTERN char e_invalid_object_variable_de EXTERN char e_not_valid_command_in_class_str[] INIT(= N_("E1318: Not a valid command in a class: %s")); EXTERN char e_using_class_as_number[] - INIT(= N_("E1319: Using a class as a Number")); + INIT(= N_("E1319: Using a Class as a Number")); EXTERN char e_using_object_as_number[] - INIT(= N_("E1320: Using an object as a Number")); + INIT(= N_("E1320: Using an Object as a Number")); EXTERN char e_using_class_as_float[] - INIT(= N_("E1321: Using a class as a Float")); + INIT(= N_("E1321: Using a Class as a Float")); EXTERN char e_using_object_as_float[] - INIT(= N_("E1322: Using an object as a Float")); + INIT(= N_("E1322: Using an Object as a Float")); EXTERN char e_using_class_as_string[] - INIT(= N_("E1323: Using a class as a String")); + INIT(= N_("E1323: Using a Class as a String")); EXTERN char e_using_object_as_string[] - INIT(= N_("E1324: Using an object as a String")); + INIT(= N_("E1324: Using an Object as a String")); EXTERN char e_method_not_found_on_class_str_str[] INIT(= N_("E1325: Method \"%s\" not found in class \"%s\"")); EXTERN char e_variable_not_found_on_object_str_str[] @@ -3538,8 +3538,22 @@ EXTERN char e_cannot_lock_object_variabl INIT(= N_("E1391: Cannot (un)lock variable \"%s\" in class \"%s\"")); EXTERN char e_cannot_lock_class_variable_str[] INIT(= N_("E1392: Cannot (un)lock class variable \"%s\" in class \"%s\"")); -#endif -// E1393 - E1499 unused (reserved for Vim9 class support) +EXTERN char e_type_can_only_be_defined_in_vim9_script[] + 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_typealias_already_exists_for_str[] + INIT(= N_("E1396: Type alias \"%s\" already exists")); +EXTERN char e_missing_typealias_name[] + INIT(= N_("E1397: Missing type alias name")); +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_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 @@ -1883,6 +1883,14 @@ set_var_lval( if (eval_variable(lp->ll_name, (int)STRLEN(lp->ll_name), lp->ll_sid, &tv, &di, EVAL_VAR_VERBOSE) == OK) { + if (di != NULL && di->di_tv.v_type == VAR_TYPEALIAS) + { + semsg(_(e_using_typealias_as_variable), + di->di_tv.vval.v_typealias->ta_name); + clear_tv(&tv); + return; + } + if ((di == NULL || (!var_check_ro(di->di_flags, lp->ll_name, FALSE) && !tv_check_lock(&di->di_tv, lp->ll_name, FALSE))) @@ -2013,6 +2021,7 @@ tv_op(typval_T *tv1, typval_T *tv2, char case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: break; case VAR_BLOB: @@ -5004,6 +5013,7 @@ check_can_index(typval_T *rettv, int eva case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: if (verbose) emsg(_(e_cannot_index_special_variable)); return FAIL; @@ -5109,6 +5119,7 @@ eval_index_inner( case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: break; // not evaluating, skipping over subscript case VAR_NUMBER: @@ -6046,6 +6057,7 @@ set_ref_in_item( case VAR_FLOAT: case VAR_STRING: case VAR_BLOB: + case VAR_TYPEALIAS: case VAR_INSTR: // Types that do not contain any other item break; @@ -6329,6 +6341,13 @@ echo_string_core( *tofree = NULL; r = (char_u *)get_var_special_name(tv->vval.v_number); break; + + case VAR_TYPEALIAS: + *tofree = vim_strsave(tv->vval.v_typealias->ta_name); + r = *tofree; + if (r == NULL) + r = (char_u *)""; + break; } if (--recurse == 0) @@ -7201,6 +7220,7 @@ item_copy( case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: copy_tv(from, to); break; case VAR_LIST: diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -3890,6 +3890,12 @@ f_empty(typval_T *argvars, typval_T *ret || !channel_is_open(argvars[0].vval.v_channel); break; #endif + case VAR_TYPEALIAS: + n = argvars[0].vval.v_typealias == NULL + || argvars[0].vval.v_typealias->ta_name == NULL + || *argvars[0].vval.v_typealias->ta_name == NUL; + break; + case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: @@ -7539,6 +7545,7 @@ f_len(typval_T *argvars, typval_T *rettv case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: emsg(_(e_invalid_type_for_len)); break; } @@ -10885,6 +10892,7 @@ f_type(typval_T *argvars, typval_T *rett case VAR_INSTR: n = VAR_TYPE_INSTR; break; case VAR_CLASS: n = VAR_TYPE_CLASS; break; case VAR_OBJECT: n = VAR_TYPE_OBJECT; break; + case VAR_TYPEALIAS: n = VAR_TYPE_TYPEALIAS; break; case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -158,6 +158,7 @@ static struct vimvar {VV_NAME("sizeofpointer", VAR_NUMBER), NULL, VV_RO}, {VV_NAME("maxcol", VAR_NUMBER), NULL, VV_RO}, {VV_NAME("python3_version", VAR_NUMBER), NULL, VV_RO}, + {VV_NAME("t_typealias", VAR_NUMBER), NULL, VV_RO}, }; // shorthand @@ -260,6 +261,7 @@ evalvars_init(void) set_vim_var_nr(VV_TYPE_BLOB, VAR_TYPE_BLOB); set_vim_var_nr(VV_TYPE_CLASS, VAR_TYPE_CLASS); set_vim_var_nr(VV_TYPE_OBJECT, VAR_TYPE_OBJECT); + set_vim_var_nr(VV_TYPE_TYPEALIAS, VAR_TYPE_TYPEALIAS); set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); @@ -1834,6 +1836,12 @@ ex_let_one( return NULL; } + if (tv->v_type == VAR_TYPEALIAS) + { + semsg(_(e_using_typealias_as_variable), tv->vval.v_typealias->ta_name); + return NULL; + } + if (*arg == '$') { // ":let $VAR = expr": Set environment variable. @@ -2331,6 +2339,7 @@ item_lock(typval_T *tv, int deep, int lo case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: break; case VAR_BLOB: @@ -2998,7 +3007,7 @@ eval_variable( } // Check for local variable when debugging. - if ((tv = lookup_debug_var(name)) == NULL) + if ((sid == 0) && (tv = lookup_debug_var(name)) == NULL) { // Check for user-defined variables. dictitem_T *v = find_var(name, &ht, flags & EVAL_VAR_NOAUTOLOAD); @@ -3114,6 +3123,25 @@ eval_variable( } } + if ((tv->v_type == VAR_TYPEALIAS || tv->v_type == VAR_CLASS) + && sid != 0) + { + // type alias or class imported from another script. Check + // whether it is exported from the other script. + sv = find_typval_in_script(tv, sid, TRUE); + if (sv == NULL) + { + ret = FAIL; + goto done; + } + if ((sv->sv_flags & SVFLAG_EXPORTED) == 0) + { + semsg(_(e_item_not_exported_in_script_str), name); + ret = FAIL; + goto done; + } + } + // If a list or dict variable wasn't initialized and has meaningful // type, do it now. Not for global variables, they are not // declared. @@ -3162,6 +3190,7 @@ eval_variable( } } +done: if (len > 0) name[len] = cc; @@ -3948,6 +3977,14 @@ set_var_const( goto failed; } + if (di->di_tv.v_type == VAR_TYPEALIAS) + { + semsg(_(e_using_typealias_as_variable), + di->di_tv.vval.v_typealias->ta_name); + clear_tv(&di->di_tv); + goto failed; + } + if (var_in_vim9script && (flags & ASSIGN_FOR_LOOP) == 0) { where_T where = WHERE_INIT; diff --git a/src/if_py_both.h b/src/if_py_both.h --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -6772,6 +6772,7 @@ ConvertToPyObject(typval_T *tv) case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: Py_INCREF(Py_None); return Py_None; case VAR_BOOL: diff --git a/src/json.c b/src/json.c --- a/src/json.c +++ b/src/json.c @@ -310,6 +310,7 @@ json_encode_item(garray_T *gap, typval_T case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: semsg(_(e_cannot_json_encode_str), vartype_name(val->v_type)); return FAIL; diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -4,6 +4,8 @@ void ex_class(exarg_T *eap); type_T *oc_member_type(class_T *cl, int is_object, char_u *name, char_u *name_end, int *member_idx); type_T *oc_member_type_by_idx(class_T *cl, int is_object, int member_idx); void ex_enum(exarg_T *eap); +void typealias_free(typealias_T *ta); +void typealias_unref(typealias_T *ta); void ex_type(exarg_T *eap); int class_object_index(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose); ufunc_T *find_class_func(char_u **arg); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1468,6 +1468,7 @@ typedef struct ectx_S ectx_T; typedef struct instr_S instr_T; typedef struct class_S class_T; typedef struct object_S object_T; +typedef struct typealias_S typealias_T; typedef enum { @@ -1489,6 +1490,7 @@ typedef enum VAR_INSTR, // "v_instr" is used VAR_CLASS, // "v_class" is used (also used for interface) VAR_OBJECT, // "v_object" is used + VAR_TYPEALIAS // "v_typealias" is used } vartype_T; // A type specification. @@ -1602,6 +1604,13 @@ struct object_S int obj_copyID; // used by garbage collection }; +struct typealias_S +{ + int ta_refcount; + type_T *ta_type; + char_u *ta_name; +}; + /* * Structure to hold an internal variable without a name. */ @@ -1625,6 +1634,7 @@ struct typval_S instr_T *v_instr; // instructions to execute class_T *v_class; // class value (can be NULL) object_T *v_object; // object value (can be NULL) + typealias_T *v_typealias; // user-defined type name } vval; }; 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 @@ -170,7 +170,7 @@ def Test_class_basic() if A endif END - v9.CheckSourceFailure(lines, 'E1319: Using a class as a Number', 4) + v9.CheckSourceFailure(lines, 'E1319: Using a Class as a Number', 4) # Test for using object as a bool lines =<< trim END @@ -181,7 +181,7 @@ def Test_class_basic() if a endif END - v9.CheckSourceFailure(lines, 'E1320: Using an object as a Number', 5) + v9.CheckSourceFailure(lines, 'E1320: Using an Object as a Number', 5) # Test for using class as a float lines =<< trim END @@ -190,7 +190,7 @@ def Test_class_basic() endclass sort([1.1, A], 'f') END - v9.CheckSourceFailure(lines, 'E1321: Using a class as a Float', 4) + v9.CheckSourceFailure(lines, 'E1321: Using a Class as a Float', 4) # Test for using object as a float lines =<< trim END @@ -200,7 +200,7 @@ def Test_class_basic() var a = A.new() sort([1.1, a], 'f') END - v9.CheckSourceFailure(lines, 'E1322: Using an object as a Float', 5) + v9.CheckSourceFailure(lines, 'E1322: Using an Object as a Float', 5) # Test for using class as a string lines =<< trim END @@ -209,7 +209,7 @@ def Test_class_basic() endclass :exe 'call ' .. A END - v9.CheckSourceFailure(lines, 'E1323: Using a class as a String', 4) + v9.CheckSourceFailure(lines, 'E1323: Using a Class as a String', 4) # Test for using object as a string lines =<< trim END @@ -219,7 +219,7 @@ def Test_class_basic() var a = A.new() :exe 'call ' .. a END - v9.CheckSourceFailure(lines, 'E1324: Using an object as a String', 5) + v9.CheckSourceFailure(lines, 'E1324: Using an Object as a String', 5) # Test creating a class with member variables and methods, calling a object # method. Check for using type() and typename() with a class and an object. 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 @@ -4722,7 +4722,7 @@ def Test_defer_after_exception() assert_equal([2, 3, 1, 4, 5, 6, 7], callTrace) END - v9.CheckScriptSuccess(lines) + v9.CheckSourceSuccess(lines) enddef " Test for multiple deferred function which throw exceptions. @@ -4780,6 +4780,384 @@ def Test_multidefer_with_exception() assert_equal('E605: Exception not caught: InnerException', v:errmsg) assert_equal([11, 9, 10, 7, 8, 5, 1, 3, 4, 12, 15, 16], callTrace) END + v9.CheckSourceSuccess(lines) +enddef + +" Test for :type command to create type aliases +def Test_typealias() + 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 + enddef + 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) + END + v9.CheckSourceSuccess(lines) + + # Use :type outside a Vim9 script + lines =<< trim END + type Index = number + END + v9.CheckSourceFailure(lines, 'E1393: Type can only be defined in Vim9 script', 1) + + # Use :type without any arguments + lines =<< trim END + vim9script + type + END + v9.CheckSourceFailure(lines, 'E1397: Missing type alias name', 2) + + # Use :type with a name but no type + lines =<< trim END + vim9script + type MyType + END + v9.CheckSourceFailure(lines, "E398: Missing '=': ", 2) + + # Use :type with a name but no type following "=" + lines =<< trim END + vim9script + type MyType = + END + v9.CheckSourceFailure(lines, 'E1398: Missing type alias type', 2) + + # No space before or after "=" + lines =<< trim END + vim9script + type MyType=number + END + v9.CheckSourceFailure(lines, 'E1315: White space required after name: MyType=number', 2) + + # No space after "=" + lines =<< trim END + vim9script + type MyType =number + END + v9.CheckSourceFailure(lines, "E1069: White space required after '=': =number", 2) + + # type alias without "=" + lines =<< trim END + vim9script + type Index number + END + v9.CheckSourceFailure(lines, "E398: Missing '=': number", 2) + + # type alias for a non-existing type + lines =<< trim END + vim9script + type Index = integer + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: integer', 2) + + # 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) + + # No white space following the alias name + lines =<< trim END + vim9script + type Index:number + END + v9.CheckSourceFailure(lines, 'E1315: White space required after name: Index:number', 2) + + # something following the type alias + lines =<< trim END + vim9script + type ListOfNums = list string + END + v9.CheckSourceFailure(lines, 'E488: Trailing characters: string', 2) + + # type alias name collides with a variable name + lines =<< trim END + vim9script + var ListOfNums: number = 10 + type ListOfNums = list + END + v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "ListOfNums"', 3) + + # duplicate type alias name + lines =<< trim END + vim9script + type MyList = list + type MyList = list + END + v9.CheckSourceFailure(lines, 'E1396: Type alias "MyList" already exists', 3) + + # Sourcing a script twice (which will free script local variables) + lines =<< trim END + vim9script + class C + endclass + type AC = C + assert_equal('typealias>', typename(AC)) + END + new + setline(1, lines) + :source + :source + bw! + + # 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) + + # Assigning a type alias (def function level) + lines =<< trim END + vim9script + type A = list + def Foo() + var x = A + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1395: Type alias "A" cannot be used as a variable', 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') + END + v9.CheckSourceSuccess(lines) + + # Using type alias in an expression (def function level) + lines =<< trim END + vim9script + type MyType = list + def Foo() + var x = MyType + 1 + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1395: Type alias "MyType" cannot be used as a variable', 1) + + # Using type alias in an expression (def function level) + lines =<< trim END + vim9script + type MyType = list + def Foo() + MyType = list + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E46: Cannot change read-only variable "MyType"', 1) + + # Using type alias in an expression (def function level) + lines =<< trim END + vim9script + type MyType = list + def Foo() + MyType += 10 + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E46: Cannot change read-only variable "MyType"', 1) + + # Creating a typealias in a def function + lines =<< trim END + vim9script + def Foo() + var n: number = 10 + type A = list + enddef + defcompile + END + v9.CheckSourceFailure(lines, 'E1399: Type can only be used in a script', 2) + + # json_encode should fail with a type alias + lines =<< trim END + vim9script + type A = list + var x = json_encode(A) + END + v9.CheckSourceFailure(lines, 'E1161: Cannot json encode a typealias', 3) + + # Comparing type alias with a number (script level) + lines =<< trim END + vim9script + type A = list + var n: number + var x = A == n + END + v9.CheckSourceFailure(lines, 'E1072: Cannot compare typealias with number', 4) + + # Comparing type alias with a number (def function level) + lines =<< trim END + vim9script + type A = list + def Foo() + var n: number + var x = A == n + enddef + Foo() + END + v9.CheckSourceFailure(lines, 'E1395: Type alias "A" cannot be used as a variable', 2) +enddef + +" Test for exporting and importing type aliases +def Test_import_typealias() + var lines =<< trim END + vim9script + export type MyType = list + END + writefile(lines, 'Xtypeexport.vim', 'D') + + lines =<< trim END + vim9script + import './Xtypeexport.vim' as A + + var myList: A.MyType = [1, 2, 3] + def Foo(l: A.MyType) + assert_equal([1, 2, 3], l) + enddef + Foo(myList) + END + v9.CheckScriptSuccess(lines) + + # Use a non existing type alias + lines =<< trim END + vim9script + import './Xtypeexport.vim' as A + + var myNum: A.SomeType = 10 + END + v9.CheckScriptFailure(lines, 'E1010: Type not recognized: A.SomeType = 10', 4) + + # Use a type alias that is not exported + lines =<< trim END + vim9script + type NewType = dict + END + writefile(lines, 'Xtypeexport2.vim', 'D') + lines =<< trim END + vim9script + import './Xtypeexport2.vim' as A + + var myDict: A.NewType = {} + END + v9.CheckScriptFailure(lines, 'E1049: Item not exported in script: NewType', 4) + + # Using the same name as an imported type alias + lines =<< trim END + vim9script + export type MyType2 = list + END + writefile(lines, 'Xtypeexport3.vim', 'D') + lines =<< trim END + vim9script + import './Xtypeexport3.vim' as A + + type MyType2 = A.MyType2 + var myList1: A.MyType2 = [1, 2, 3] + var myList2: MyType2 = [4, 5, 6] + assert_equal([1, 2, 3], myList1) + assert_equal([4, 5, 6], myList2) + END + v9.CheckScriptSuccess(lines) +enddef + +" Test for using typealias as a def function argument and return type +def Test_typealias_func_argument() + var lines =<< trim END + vim9script + type A = list + def Foo(l: A): A + assert_equal([1, 2], l) + return l + enddef + var x: A = [1, 2] + assert_equal([1, 2], Foo(x)) + END + v9.CheckScriptSuccess(lines) + + # passing a type alias variable to a function expecting a specific type + lines =<< trim END + vim9script + type A = list + def Foo(l: list) + assert_equal([1, 2], l) + enddef + var x: A = [1, 2] + Foo(x) + END + v9.CheckScriptSuccess(lines) + + # passing a type alias variable to a function expecting any + lines =<< trim END + vim9script + type A = list + def Foo(l: any) + assert_equal([1, 2], l) + enddef + var x: A = [1, 2] + Foo(x) + END + v9.CheckScriptSuccess(lines) +enddef + +" Using a type alias with a builtin function +def Test_typealias_with_builtin_functions() + var lines =<< trim END + vim9script + type A = list + assert_equal(0, empty(A)) + END + v9.CheckScriptSuccess(lines) + + # Using a type alias with len() + lines =<< trim END + vim9script + type A = list + var x = len(A) + END + v9.CheckScriptFailure(lines, 'E701: Invalid type for len()', 3) + + # Using a type alias with eval() + lines =<< trim END + vim9script + type A = number + def Foo() + var x = eval("A") + enddef + Foo() + END + v9.CheckScriptFailure(lines, 'E1395: Type alias "A" cannot be used as a variable', 1) +enddef + +" Test for type alias refcount +def Test_typealias_refcount() + var lines =<< trim END + vim9script + type A = list + assert_equal(1, test_refcount(A)) + END + v9.CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + type B = list + var x: B = [] + assert_equal(1, test_refcount(B)) + END v9.CheckScriptSuccess(lines) enddef diff --git a/src/testing.c b/src/testing.c --- a/src/testing.c +++ b/src/testing.c @@ -1132,6 +1132,10 @@ f_test_refcount(typval_T *argvars, typva if (argvars[0].vval.v_dict != NULL) retval = argvars[0].vval.v_dict->dv_refcount - 1; break; + case VAR_TYPEALIAS: + if (argvars[0].vval.v_typealias != NULL) + retval = argvars[0].vval.v_typealias->ta_refcount - 1; + break; } rettv->v_type = VAR_NUMBER; diff --git a/src/typval.c b/src/typval.c --- a/src/typval.c +++ b/src/typval.c @@ -92,6 +92,10 @@ free_tv(typval_T *varp) object_unref(varp->vval.v_object); break; + case VAR_TYPEALIAS: + typealias_unref(varp->vval.v_typealias); + break; + case VAR_NUMBER: case VAR_FLOAT: case VAR_ANY: @@ -169,6 +173,10 @@ clear_tv(typval_T *varp) object_unref(varp->vval.v_object); varp->vval.v_object = NULL; break; + case VAR_TYPEALIAS: + typealias_unref(varp->vval.v_typealias); + varp->vval.v_typealias = NULL; + break; case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: @@ -262,6 +270,10 @@ tv_get_bool_or_number_chk( case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; + case VAR_TYPEALIAS: + semsg(_(e_using_typealias_as_variable), + varp->vval.v_typealias->ta_name); + break; case VAR_UNKNOWN: case VAR_ANY: case VAR_INSTR: @@ -379,6 +391,10 @@ tv_get_float_chk(typval_T *varp, int *er case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; + case VAR_TYPEALIAS: + semsg(_(e_using_typealias_as_variable), + varp->vval.v_typealias->ta_name); + break; case VAR_UNKNOWN: case VAR_ANY: case VAR_INSTR: @@ -1129,6 +1145,7 @@ tv_get_string_buf_chk_strict(typval_T *v case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; + case VAR_TYPEALIAS: case VAR_UNKNOWN: case VAR_ANY: case VAR_INSTR: @@ -1290,6 +1307,15 @@ copy_tv(typval_T *from, typval_T *to) ++to->vval.v_dict->dv_refcount; } break; + case VAR_TYPEALIAS: + if (from->vval.v_typealias == NULL) + to->vval.v_typealias = NULL; + else + { + to->vval.v_typealias = from->vval.v_typealias; + ++to->vval.v_typealias->ta_refcount; + } + break; case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; @@ -1596,6 +1622,7 @@ typval_compare_null(typval_T *tv1, typva case VAR_FLOAT: if (!in_vim9script()) return tv->vval.v_float == 0.0; break; + case VAR_TYPEALIAS: return tv->vval.v_typealias == NULL; default: break; } } @@ -2069,6 +2096,9 @@ tv_equal( case VAR_FUNC: return tv1->vval.v_string == tv2->vval.v_string; + case VAR_TYPEALIAS: + return tv1->vval.v_typealias == tv2->vval.v_typealias; + case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: 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 */ /**/ + 2076, +/**/ 2075, /**/ 2074, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -2142,7 +2142,8 @@ typedef int sock_T; #define VV_SIZEOFPOINTER 104 #define VV_MAXCOL 105 #define VV_PYTHON3_VERSION 106 -#define VV_LEN 107 // number of v: vars +#define VV_TYPE_TYPEALIAS 107 +#define VV_LEN 108 // number of v: vars // used for v_number in VAR_BOOL and VAR_SPECIAL #define VVAL_FALSE 0L // VAR_BOOL @@ -2165,6 +2166,7 @@ typedef int sock_T; #define VAR_TYPE_INSTR 11 #define VAR_TYPE_CLASS 12 #define VAR_TYPE_OBJECT 13 +#define VAR_TYPE_TYPEALIAS 15 #define DICT_MAXNEST 100 // maximum nesting of lists and dicts diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -2095,12 +2095,119 @@ ex_enum(exarg_T *eap UNUSED) } /* - * Handle ":type". + * Type aliases (:type) + */ + + void +typealias_free(typealias_T *ta) +{ + // ta->ta_type is freed in clear_type_list() + vim_free(ta->ta_name); + vim_free(ta); +} + + void +typealias_unref(typealias_T *ta) +{ + if (ta != NULL && --ta->ta_refcount <= 0) + typealias_free(ta); +} + +/* + * Handle ":type". Create an alias for a type specification. */ void ex_type(exarg_T *eap UNUSED) { - // TODO + char_u *arg = eap->arg; + + if (!current_script_is_vim9() + || (cmdmod.cmod_flags & CMOD_LEGACY) + || !getline_equal(eap->getline, eap->cookie, getsourceline)) + { + emsg(_(e_type_can_only_be_defined_in_vim9_script)); + return; + } + + if (*arg == NUL) + { + emsg(_(e_missing_typealias_name)); + return; + } + + if (!ASCII_ISUPPER(*arg)) + { + semsg(_(e_type_name_must_start_with_uppercase_letter_str), arg); + return; + } + + char_u *name_end = find_name_end(arg, NULL, NULL, FNE_CHECK_START); + if (!IS_WHITE_OR_NUL(*name_end)) + { + semsg(_(e_white_space_required_after_name_str), arg); + return; + } + char_u *name_start = arg; + + arg = skipwhite(name_end); + if (*arg != '=') + { + semsg(_(e_missing_equal_str), arg); + return; + } + if (!IS_WHITE_OR_NUL(*(arg + 1))) + { + semsg(_(e_white_space_required_after_str_str), "=", arg); + return; + } + arg++; + arg = skipwhite(arg); + + if (*arg == NUL) + { + emsg(_(e_missing_typealias_type)); + return; + } + + scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid); + type_T *type = parse_type(&arg, &si->sn_type_list, TRUE); + if (type == NULL) + return; + + if (*arg != NUL) + { + // some text after the type + semsg(_(e_trailing_characters_str), arg); + return; + } + + int cc = *name_end; + *name_end = NUL; + + typval_T tv; + tv.v_type = VAR_UNKNOWN; + if (eval_variable_import(name_start, &tv) == OK) + { + if (tv.v_type == VAR_TYPEALIAS) + semsg(_(e_typealias_already_exists_for_str), name_start); + else + semsg(_(e_redefining_script_item_str), name_start); + clear_tv(&tv); + 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; + set_var_const(name_start, current_sctx.sc_sid, NULL, &tv, FALSE, + ASSIGN_CONST | ASSIGN_FINAL, 0); + +done: + *name_end = cc; } /* diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1327,11 +1327,12 @@ assignment_len(char_u *p, int *heredoc) /* * Generate the load instruction for "name". */ - static void + static int generate_loadvar(cctx_T *cctx, lhs_T *lhs) { char_u *name = lhs->lhs_name; type_T *type = lhs->lhs_type; + int res = OK; switch (lhs->lhs_dest) { @@ -1360,7 +1361,7 @@ generate_loadvar(cctx_T *cctx, lhs_T *lh generate_LOAD(cctx, ISN_LOADT, 0, name + 2, type); break; case dest_script: - compile_load_scriptvar(cctx, + res = compile_load_scriptvar(cctx, name + (name[1] == ':' ? 2 : 0), NULL, NULL); break; case dest_env: @@ -1392,6 +1393,8 @@ generate_loadvar(cctx_T *cctx, lhs_T *lh // list or dict value should already be on the stack. break; } + + return res; } /* @@ -2240,10 +2243,11 @@ compile_load_lhs( && need_type(rhs_type, member_type, FALSE, -3, 0, cctx, FALSE, FALSE) == FAIL) return FAIL; + + return OK; } - else - generate_loadvar(cctx, lhs); - return OK; + + return generate_loadvar(cctx, lhs); } /* @@ -2301,7 +2305,8 @@ compile_load_lhs_with_index(lhs_T *lhs, return generate_CLASSMEMBER(cctx, TRUE, cl, lhs->lhs_member_idx); } - compile_load_lhs(lhs, var_start, NULL, cctx); + if (compile_load_lhs(lhs, var_start, NULL, cctx) == FAIL) + return FAIL; if (lhs->lhs_has_index) { @@ -2510,6 +2515,7 @@ push_default_value( case VAR_VOID: case VAR_INSTR: case VAR_CLASS: + case VAR_TYPEALIAS: case VAR_SPECIAL: // cannot happen // This is skipped for local variables, they are always // initialized to zero. But in a "for" or "while" loop @@ -3963,6 +3969,11 @@ compile_def_function( line = (char_u *)""; break; + case CMD_type: + emsg(_(e_type_can_only_be_used_in_script)); + goto erret; + break; + case CMD_global: if (check_global_and_subst(ea.cmd, p) == FAIL) goto erret; diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -3807,6 +3807,13 @@ 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) + { + semsg(_(e_using_typealias_as_variable), + STACK_TV_BOT(0)->vval.v_typealias->ta_name); + clear_tv(STACK_TV_BOT(0)); + goto on_error; + } clear_tv(tv); *tv = *STACK_TV_BOT(0); break; @@ -7517,6 +7524,7 @@ tv2bool(typval_T *tv) case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: break; } return FALSE; diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -238,6 +238,7 @@ compile_member(int is_slice, int *keepin case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: @@ -529,6 +530,13 @@ 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/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -240,6 +240,7 @@ may_generate_2STRING(int offset, int tol case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: to_string_error(type->tt_type); return FAIL; } diff --git a/src/vim9type.c b/src/vim9type.c --- a/src/vim9type.c +++ b/src/vim9type.c @@ -1354,7 +1354,9 @@ parse_type(char_u **arg, garray_T *type_ } // It can be a class or interface name, possibly imported. - typval_T tv; + int did_emsg_before = did_emsg; + typval_T tv; + tv.v_type = VAR_UNKNOWN; if (eval_variable_import(*arg, &tv) == OK) { @@ -1377,11 +1379,22 @@ parse_type(char_u **arg, garray_T *type_ return type; } } + else if (tv.v_type == VAR_TYPEALIAS) + { + // user defined type + type_T *type = copy_type(tv.vval.v_typealias->ta_type, type_gap); + *arg += len; + clear_tv(&tv); + // Skip over ".TypeName". + while (ASCII_ISALNUM(**arg) || **arg == '_' || **arg == '.') + ++*arg; + return type; + } clear_tv(&tv); } - if (give_error) + if (give_error && (did_emsg == did_emsg_before)) semsg(_(e_type_not_recognized_str), *arg); return NULL; } @@ -1416,6 +1429,7 @@ equal_type(type_T *type1, type_T *type2, case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: + case VAR_TYPEALIAS: break; // not composite is always OK case VAR_LIST: case VAR_DICT: @@ -1666,6 +1680,7 @@ vartype_name(vartype_T type) case VAR_INSTR: return "instr"; case VAR_CLASS: return "class"; case VAR_OBJECT: return "object"; + case VAR_TYPEALIAS: return "typealias"; case VAR_FUNC: case VAR_PARTIAL: return "func"; @@ -1795,12 +1810,25 @@ f_typename(typval_T *argvars, typval_T * rettv->v_type = VAR_STRING; ga_init2(&type_list, sizeof(type_T *), 10); - type = typval2type(argvars, get_copyID(), &type_list, TVTT_DO_MEMBER); + if (argvars[0].v_type == VAR_TYPEALIAS) + type = argvars[0].vval.v_typealias->ta_type; + else + type = typval2type(argvars, get_copyID(), &type_list, TVTT_DO_MEMBER); name = type_name(type, &tofree); - if (tofree != NULL) - rettv->vval.v_string = (char_u *)tofree; + if (argvars[0].v_type == VAR_TYPEALIAS) + { + vim_snprintf((char *)IObuff, IOSIZE, "typealias<%s>", name); + rettv->vval.v_string = vim_strsave((char_u *)IObuff); + if (tofree != NULL) + vim_free(tofree); + } else - rettv->vval.v_string = vim_strsave((char_u *)name); + { + if (tofree != NULL) + rettv->vval.v_string = (char_u *)tofree; + else + rettv->vval.v_string = vim_strsave((char_u *)name); + } clear_type_list(&type_list); } diff --git a/src/viminfo.c b/src/viminfo.c --- a/src/viminfo.c +++ b/src/viminfo.c @@ -1374,7 +1374,8 @@ write_viminfo_varlist(FILE *fp) case VAR_INSTR: case VAR_CLASS: case VAR_OBJECT: - continue; + case VAR_TYPEALIAS: + continue; } fprintf(fp, "!%s\t%s\t", this_var->di_key, s); if (this_var->di_tv.v_type == VAR_BOOL