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<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>
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index cac8484..2bc9b14 100644
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -4722,7 +4722,7 @@
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 @@
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