view src/testdir/test_vim9_typealias.vim @ 34074:1629cc65d78d v9.1.0006

patch 9.1.0006: is*() and to*() function may be unsafe Commit: https://github.com/vim/vim/commit/184f71cc6868a240dc872ed2852542bbc1d43e28 Author: Keith Thompson <Keith.S.Thompson@gmail.com> Date: Thu Jan 4 21:19:04 2024 +0100 patch 9.1.0006: is*() and to*() function may be unsafe Problem: is*() and to*() function may be unsafe Solution: Add SAFE_* macros and start using those instead (Keith Thompson) Use SAFE_() macros for is*() and to*() functions The standard is*() and to*() functions declared in <ctype.h> have undefined behavior for negative arguments other than EOF. If plain char is signed, passing an unchecked value from argv for from user input to one of these functions has undefined behavior. Solution: Add SAFE_*() macros that cast the argument to unsigned char. Most implementations behave sanely for negative arguments, and most character values in practice are non-negative, but it's still best to avoid undefined behavior. The change from #13347 has been omitted, as this has already been separately fixed in commit ac709e2fc0db6d31abb7da96f743c40956b60c3a (v9.0.2054) fixes: #13332 closes: #13347 Signed-off-by: Keith Thompson <Keith.S.Thompson@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Thu, 04 Jan 2024 21:30:04 +0100
parents ab6a70fad5b5
children be4389b04043
line wrap: on
line source

" Test Vim9 type aliases

source check.vim
import './vim9.vim' as v9

" 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<string>
    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<list<string>>', typename(ListOfStrings))
    assert_equal(v:t_typealias, type(ListOfStrings))
    assert_equal('ListOfStrings', string(ListOfStrings))
    assert_fails('var x = null == ListOfStrings', 'E1403: Type alias "ListOfStrings" cannot be used as a value')
  END
  v9.CheckSourceSuccess(lines)

  # Use type alias at def function level
  lines =<< trim END
    vim9script
    type ListOfStrings = list<string>
    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<list<string>>', 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
  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)

  # def function argument name collision with a type alias
  lines =<< trim END
    vim9script
    type A = list<number>
    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<number>
    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<number> = []
    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('class<C>', 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<number>
    MyType = [1, 2, 3]
  END
  v9.CheckSourceFailure(lines, 'E1403: Type alias "MyType" cannot be used as a value', 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, 'E1407: Cannot use a Typealias as a variable or value', 1)

  # Using type alias in an expression (script level)
  lines =<< trim END
    vim9script
    type MyType = list<number>
    assert_fails('var m = MyType', 'E1403: Type alias "MyType" cannot be used as a value')
    assert_fails('var i = MyType + 1', 'E1403: Type alias "MyType" cannot be used as a value')
    assert_fails('var f = 1.0 + MyType', 'E1403: Type alias "MyType" cannot be used as a value')
    assert_fails('MyType += 10', 'E1403: Type alias "MyType" cannot be used as a value')
    assert_fails('var x = $"-{MyType}-"', 'E1403: Type alias "MyType" cannot be used as a value')
    assert_fails('var x = MyType[1]', 'E1403: Type alias "MyType" cannot be used as a value')
  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, 'E1407: Cannot use a Typealias as a variable or value', 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)

  # Convert type alias to a string (def function level)
  lines =<< trim END
    vim9script
    type MyType = list<number>
    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, 'E1403: Type alias "B" cannot be used as a value', 3)

  # 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, 'E1403: Type alias "A" cannot be used as a value', 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, 'E1403: Type alias "A" cannot be used as a value', 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, 'E1407: Cannot use a Typealias as a variable or value', 2)

  # casting a number to a type alias (script level)
  lines =<< trim END
    vim9script
    type MyType = bool
    assert_equal(true, <MyType>1 == true)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for exporting and importing type aliases
def Test_typealias_import()
  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)

  # Using an exported class to create a type alias
  lines =<< trim END
    vim9script
    export class MyClass
      var 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 = MyType3.new()
    assert_equal(10, c.val)
  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>
    var x = empty(A)
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 3)

  # Using a type alias with len()
  lines =<< trim END
    vim9script
    type A = list<func>
    var x = len(A)
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 3)

  # Using a type alias with len()
  lines =<< trim END
    vim9script
    type A = list<func>
    def Foo()
      var x = len(A)
    enddef
    Foo()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1)

  # 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, 'E1403: Type alias "A" cannot be used as a value', 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

" 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: Class or class typealias required for argument 2')
    assert_fails('instanceof(o, Ctype, Ntype)', 'E693: Class or class typealias required for argument 3')

    def F()
      var x = instanceof(o, Ntype)
    enddef
    assert_fails('F()', 'E693: Class or class typealias required for argument 2')

    def G(): bool
      return instanceof(o, Ctype)
    enddef
    assert_equal(1, G())
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for type aliasing a class
def Test_typealias_class()
  var lines =<< trim END
    vim9script
    class C
      var color = 'green'
    endclass
    type MyClass = C
    var o: MyClass = MyClass.new()
    assert_equal('green', o.color)
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for typealias as function arg and return value
def Test_type_as_func_argument_or_return_value()
  # check typealias as arg, function call in script level
  var lines =<< trim END
    vim9script
    type A = number
    def Foo(arg: any)
    enddef
    Foo(A)
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 5)

  # check typealias as function return, function call in script level
  lines =<< trim END
    vim9script
    type A = number
    def Foo(): any
      return A
    enddef
    Foo()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1)

  # check typealias as arg, function call in :def
  lines =<< trim END
    vim9script
    type A = number
    def Foo(arg: any)
    enddef
    def F()
      Foo(A)
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1)

  # check typealias as function return, function call in :def
  lines =<< trim END
    vim9script
    type A = number
    def Foo(): any
      return A
    enddef
    def F()
      Foo()
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1)

  # check funcref using typealias as arg at script level
  lines =<< trim END
    vim9script
    type A = number
    def F(arg: any)
      echo typename(arg)
    enddef
    var Fref: func(any)
    Fref = F

    Fref(A)
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 9)

  # check funcref using typealias as arg in :def
  lines =<< trim END
    vim9script
    type A = number
    def F(arg: any)
      echo typename(arg)
    enddef
    var Fref: func(any)
    Fref = F

    def G()
      Fref(A)
    enddef
    G()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1)

  # check funcref using typealias as return
  lines =<< trim END
    vim9script
    type A = number
    def F(): any
      return A
    enddef
    var Fref: func(): any
    Fref = F

    Fref()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1)

  # check defered function using typealias as arg
  lines =<< trim END
    vim9script
    type A = number
    def F(arg: any)
    enddef
    def G()
      defer F(A)
    enddef
    G()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1)
enddef

" Test for class typealias as function arg and return value
def Test_class_as_func_argument_or_return_value()
  # check class typealias as arg, function call in script level
  var lines =<< trim END
    vim9script
    class C
    endclass
    type A = C
    def Foo(arg: any)
    enddef
    Foo(A)
  END
  v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value', 7)

  # check class typealias as function return, function call in script level
  lines =<< trim END
    vim9script
    class C
    endclass
    type A = C
    def Foo(): any
      return A
    enddef
    Foo()
  END
  v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value', 1)

  # check class typealias as arg, function call in :def
  lines =<< trim END
    vim9script
    class C
    endclass
    type A = C
    def Foo(arg: any)
    enddef
    def F()
      Foo(A)
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value', 1)

  # check class typealias as function return, function call in :def
  lines =<< trim END
    vim9script
    class C
    endclass
    type A = C
    def Foo(): any
      return A
    enddef
    def F()
      Foo()
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value', 1)

  # check funcref using class typealias as arg at script level
  lines =<< trim END
    vim9script
    class C
    endclass
    type A = C
    def F(arg: any)
      echo typename(arg)
    enddef
    var Fref: func(any)
    Fref = F

    Fref(A)
  END
  v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value', 11)

  # check funcref using class typealias as arg in :def
  lines =<< trim END
    vim9script
    class C
    endclass
    type A = C
    def F(arg: any)
      echo typename(arg)
    enddef
    var Fref: func(any)
    Fref = F

    def G()
      Fref(A)
    enddef
    G()
  END
  v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value', 1)

  # check funcref using class typealias as return
  lines =<< trim END
    vim9script
    class C
    endclass
    type A = C
    def F(): any
      return A
    enddef
    var Fref: func(): any
    Fref = F

    Fref()
  END
  v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value', 1)

  # check defered function using class typealias as arg
  lines =<< trim END
    vim9script
    class C
    endclass
    type A = C
    def F(arg: any)
    enddef
    def G()
      defer F(A)
    enddef
    G()
  END
  v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value', 1)
enddef

def Test_passing_typealias_to_builtin()
  # type, typename, string, instanceof are allowed type argument
  var lines =<< trim END
    vim9script
    type T = number
    var x: any
    x = type(T)
    x = typename(T)
    x = string(T)
  END
  v9.CheckScriptSuccess(lines)

  # check argument to add at script level
  # Note: add() is special cased in compile_call in vim9expr
  lines =<< trim END
    vim9script
    type T = number
    add([], T)
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value')

  # check argument to add in :def
  lines =<< trim END
    vim9script
    type T = number
    def F()
      add([], T)
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value')

  # check member call argument to add at script level
  lines =<< trim END
    vim9script
    type T = number
    []->add(T)
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value')

  # check member call argument to add in :def
  lines =<< trim END
    vim9script
    type T = number
    def F()
      []->add(T)
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value')

  # Try "empty()" builtin
  # check argument to empty at script level
  lines =<< trim END
    vim9script
    type T = number
    empty(T)
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value')

  # check argument to empty in :def
  lines =<< trim END
    vim9script
    type T = number
    def F()
      empty(T)
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value')

  # check member call argument to empty at script level
  lines =<< trim END
    vim9script
    type T = number
    T->empty()
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value')

  # check member call argument to empty in :def
  lines =<< trim END
    vim9script
    type T = number
    def F()
      T->empty()
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value')

  # Try "abs()" builtin
  # check argument to abs at script level
  lines =<< trim END
    vim9script
    type T = number
    abs(T)
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value')

  # check argument to abs in :def
  lines =<< trim END
    vim9script
    type T = number
    def F()
      abs(T)
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value')

  # check member call argument to abs at script level
  lines =<< trim END
    vim9script
    type T = number
    T->abs()
  END
  v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value')

  # check member call argument to abs in :def
  lines =<< trim END
    vim9script
    type T = number
    def F()
      T->abs()
    enddef
    F()
  END
  v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value')
enddef

" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker