]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.2076: Vim9: No support for type aliases v9.0.2076
authorYegappan Lakshmanan <yegappan@yahoo.com>
Fri, 27 Oct 2023 17:35:26 +0000 (19:35 +0200)
committerChristian Brabandt <cb@256bit.org>
Fri, 27 Oct 2023 17:35:26 +0000 (19:35 +0200)
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<string>

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 <cb@256bit.org>
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
22 files changed:
runtime/doc/vim9class.txt
src/errors.h
src/eval.c
src/evalfunc.c
src/evalvars.c
src/if_py_both.h
src/json.c
src/proto/vim9class.pro
src/structs.h
src/testdir/test_vim9_class.vim
src/testdir/test_vim9_script.vim
src/testing.c
src/typval.c
src/version.c
src/vim.h
src/vim9class.c
src/vim9compile.c
src/vim9execute.c
src/vim9expr.c
src/vim9instr.c
src/vim9type.c
src/viminfo.c

index 27184c808d78debe2c4567470b1a73569f63c4bc..53b72fc062d8eac2c571401068d51e4501d855b6 100644 (file)
@@ -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.  For Example: >
+A type definition is giving a name to a type specification.  This also known
+type alias.  For Example: >
 
-       :type ListOfStrings list<string>
+       :type ListOfStrings list<string>
 
+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.
 
 ==============================================================================
 
index 72957d8b93bdcdaa71f435cf23685bb3874b30cd..4c6b63bd75fad4a29c00bc452874e4166fe173b2 100644 (file)
@@ -3384,17 +3384,17 @@ EXTERN char e_invalid_object_variable_declaration_str[]
 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_variable_str[]
        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[]
index e888fecc8ae7ae547d27eea111343326f39d746c..ce981223fddd9f0257b9a5bdc1ecd8d82c4fb414 100644 (file)
@@ -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_u *op)
            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 evaluate, int verbose)
        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:
index 9e4e26ba0c50633181957f02a763b008e63352ab..7f7914eca7d917f2eb01488fcb04af46a43df207 100644 (file)
@@ -3890,6 +3890,12 @@ f_empty(typval_T *argvars, typval_T *rettv)
                               || !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 *rettv)
        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:
index d7a1a96a541e07a55ee60f34c8f80412b96365bd..5c7bc12d2ffd38c7bf9dfc112b9c07dd84028234 100644 (file)
@@ -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 lock, int check_refcount)
        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;
index 06201711f699ab9aff7e6e660b15d120dd661bfe..f8438639e61e49b25d34d878215bcdc4b24a9ac2 100644 (file)
@@ -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:
index e2a011309c32986912a3ffd754dad3980f58846c..66b8bf9d1466651b1de93ad0c55e05e80a8e8ba6 100644 (file)
@@ -310,6 +310,7 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
        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;
 
index 62d1b7d0f31e82328f93aa078b34b11bc7dea02d..3000f57a38116e4242e2a200f48a07d128ea475d 100644 (file)
@@ -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);
index 209067b2e03e0b37d85f665b026ad4c95488c5e7..36968741e34c2c9e00b9d89b6dd5afe6082263e7 100644 (file)
@@ -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;
 };
 
index 1c309e4f0c3ff0b5d5cd5ab54bff6855045ca8fc..a8d0fd6697cfeb268426398c9c0e5ef418a2b952 100644 (file)
@@ -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.
index cac8484977b0e2059a7c74c2f611b07f225ae243..2bc9b14aa38bcc154afec2fb08de79a6ca2c14e7 100644 (file)
@@ -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<string>
+    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<list<string>>', 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<number> 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<number>
+  END
+  v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "ListOfNums"', 3)
+
+  # duplicate type alias name
+  lines =<< trim END
+    vim9script
+    type MyList = list<number>
+    type MyList = list<string>
+  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<object<C>>', typename(AC))
+  END
+  new
+  setline(1, lines)
+  :source
+  :source
+  bw!
+
+  # Assigning to a type alias (script level)
+  lines =<< trim END
+    vim9script
+    type MyType = list<number>
+    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<string>
+    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<number>
+    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<number>
+    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<number>
+    def Foo()
+      MyType = list<string>
+    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<number>
+    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<string>
+    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<string>
+    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<string>
+    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<string>
+    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<number>
+  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<string>
+  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<number>
+  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<number>
+    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<number>
+    def Foo(l: list<number>)
+      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<number>
+    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<func>
+    assert_equal(0, empty(A))
+  END
+  v9.CheckScriptSuccess(lines)
+
+  # Using a type alias with len()
+  lines =<< trim END
+    vim9script
+    type A = list<func>
+    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<func>
+    assert_equal(1, test_refcount(A))
+  END
+  v9.CheckScriptSuccess(lines)
+
+  lines =<< trim END
+    vim9script
+    type B = list<number>
+    var x: B = []
+    assert_equal(1, test_refcount(B))
+  END
   v9.CheckScriptSuccess(lines)
 enddef
 
index fd55927dfbf83b69bb69d69db3f6d2c7e61fa718..1835643689d08fd3e0c02b86dd2a070757aa6cef 100644 (file)
@@ -1132,6 +1132,10 @@ f_test_refcount(typval_T *argvars, typval_T *rettv)
            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;
index 08dd2313f26362d2951da04f8dff99aabf4588f6..da5d7affb7b03bb1477e215b9df72c9215ef5b72 100644 (file)
@@ -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 *error)
        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 *varp, char_u *buf, int strict)
        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, typval_T *tv2)
            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:
index 8c908572041e56521c5545ff1233a31700e6588c..d233fa6ab95c8f09186fc7050673869e379fdcdb 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2076,
 /**/
     2075,
 /**/
index 2b6c787b99aaba3a78692ca78b54fc591d7825e7..6fb897822429fbb56fe9115225e1bea9e55266d1 100644 (file)
--- 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
 
index a05dcce3da8256fcb647901c4c7767c6ec658147..0e8dc2df77212dcb4a9603b32ee9e4bf5fe1f50a 100644 (file)
@@ -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;
 }
 
 /*
index 4aa83606b9fbe7cc1e24e8e13c6710d0b7f7e9f3..a4ed44951d1b3ececd00001261ccbf0206845c8e 100644 (file)
@@ -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 *lhs)
            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 *lhs)
            // 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, char_u *var_start, cctx_T *cctx)
        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;
index a6bf4715a88b44bc8b4371ecb87f30faac3b15f1..7901f6e78359115aa05736a82fffe32505424164 100644 (file)
@@ -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;
index 41ed7e74014b5af6ce7ffb31838ddc0722045581..c001d9e3ddbeb43dc9b050f5bbea83fa9c33ea14 100644 (file)
@@ -238,6 +238,7 @@ compile_member(int is_slice, int *keeping_dict, cctx_T *cctx)
            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;
index 8ee9e7c6cdfc46571627d161e28e1d4028b51770..3547d42f4ad8f82ae52f826f5b1ea57e35a18f11 100644 (file)
@@ -240,6 +240,7 @@ may_generate_2STRING(int offset, int tolerant, cctx_T *cctx)
        case VAR_INSTR:
        case VAR_CLASS:
        case VAR_OBJECT:
+       case VAR_TYPEALIAS:
                         to_string_error(type->tt_type);
                         return FAIL;
     }
index c31e51b983b1f4cbaa275adbe70dbcdad52ce2f0..2a20bcb2bf10a488c230fe65b402aa9ebe12ea23 100644 (file)
@@ -1354,7 +1354,9 @@ parse_type(char_u **arg, garray_T *type_gap, int give_error)
     }
 
     // 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_gap, int give_error)
                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, int flags)
        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)
 
     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);
 }
 
index d3281564ee5a58a0ba3c5ee6b7e532032f5e8488..58bf419194a519abbbccbb6af60618772a8b63f4 100644 (file)
@@ -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