" Test Vim9 classes

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

def Test_class_basic()
  # Class supported only in "vim9script"
  var lines =<< trim END
    class NotWorking
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1316: Class can only be defined in Vim9 script', 1)

  # First character in a class name should be capitalized.
  lines =<< trim END
    vim9script
    class notWorking
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1314: Class name must start with an uppercase letter: notWorking', 2)

  # Only alphanumeric characters are supported in a class name
  lines =<< trim END
    vim9script
    class Not@working
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1315: White space required after name: Not@working', 2)

  # Unsupported keyword (instead of class)
  lines =<< trim END
    vim9script
    abstract noclass Something
    endclass
  END
  v9.CheckSourceFailure(lines, 'E475: Invalid argument: noclass Something', 2)

  # Only the complete word "class" should be recognized
  lines =<< trim END
    vim9script
    abstract classy Something
    endclass
  END
  v9.CheckSourceFailure(lines, 'E475: Invalid argument: classy Something', 2)

  # The complete "endclass" should be specified.
  lines =<< trim END
    vim9script
    class Something
    endcl
  END
  v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: endcl', 3)

  # Additional words after "endclass"
  lines =<< trim END
    vim9script
    class Something
    endclass school's out
  END
  v9.CheckSourceFailure(lines, "E488: Trailing characters: school's out", 3)

  # Additional commands after "endclass"
  lines =<< trim END
    vim9script
    class Something
    endclass | echo 'done'
  END
  v9.CheckSourceFailure(lines, "E488: Trailing characters: | echo 'done'", 3)

  # Additional command after "class name"
  lines =<< trim END
    vim9script
    class Something | var x = 10
    endclass
  END
  v9.CheckSourceFailure(lines, "E488: Trailing characters: | var x = 10", 2)

  # Additional command after "object variable"
  lines =<< trim END
    vim9script
    class Something
      var l: list<number> = [] | var y = 10
    endclass
  END
  v9.CheckSourceFailure(lines, "E488: Trailing characters: | var y = 10", 3)

  # Additional command after "class variable"
  lines =<< trim END
    vim9script
    class Something
      static var d = {a: 10} | var y = 10
    endclass
  END
  v9.CheckSourceFailure(lines, "E488: Trailing characters: | var y = 10", 3)

  # Additional command after "object method"
  lines =<< trim END
    vim9script
    class Something
      def Foo() | var y = 10
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, "E488: Trailing characters: | var y = 10", 3)

  # Comments are allowed after an inline block
  lines =<< trim END
    vim9script
    class Foo
      static const bar = { # {{{
        baz: 'qux'
      } # }}}
    endclass
    assert_equal({baz: 'qux'}, Foo.bar)
  END
  v9.CheckSourceSuccess(lines)

  # Try to define a class with the same name as an existing variable
  lines =<< trim END
    vim9script
    var Something: list<number> = [1]
    class Thing
    endclass
    interface Api
    endinterface
    class Something extends Thing implements Api
      var v1: string = ''
      def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "Something"', 7)

  # Use old "this." prefixed member variable declaration syntax (without initialization)
  lines =<< trim END
    vim9script
    class Something
      this.count: number
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: this.count: number', 3)

  # Use old "this." prefixed member variable declaration syntax (with initialization)
  lines =<< trim END
    vim9script
    class Something
      this.count: number = 42
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: this.count: number = 42', 3)

  # Use old "this." prefixed member variable declaration syntax (type inferred)
  lines =<< trim END
    vim9script
    class Something
      this.count = 42
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: this.count = 42', 3)

  # Use "this" without any member variable name
  lines =<< trim END
    vim9script
    class Something
      this
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: this', 3)

  # Use "this." without any member variable name
  lines =<< trim END
    vim9script
    class Something
      this.
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: this.', 3)

  # Space between "this" and ".<variable>"
  lines =<< trim END
    vim9script
    class Something
      this .count
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: this .count', 3)

  # Space between "this." and the member variable name
  lines =<< trim END
    vim9script
    class Something
      this. count
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: this. count', 3)

  # Use "that" instead of "this"
  lines =<< trim END
    vim9script
    class Something
      var count: number
      that.count
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: that.count', 4)

  # Use "variable" instead of "var" for member variable declaration (without initialization)
  lines =<< trim END
    vim9script
    class Something
      variable count: number
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: variable count: number', 3)

  # Use "variable" instead of "var" for member variable declaration (with initialization)
  lines =<< trim END
    vim9script
    class Something
      variable count: number = 42
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: variable count: number = 42', 3)

  # Use "variable" instead of "var" for member variable declaration (type inferred)
  lines =<< trim END
    vim9script
    class Something
      variable count = 42
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: variable count = 42', 3)

  # Use a non-existing member variable in new()
  lines =<< trim END
    vim9script
    class Something
      def new()
        this.state = 0
      enddef
    endclass
    var obj = Something.new()
  END
  v9.CheckSourceFailure(lines, 'E1326: Variable "state" not found in object "Something"', 1)

  # Space before ":" in a member variable declaration
  lines =<< trim END
    vim9script
    class Something
      var count : number
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1059: No white space allowed before colon: count : number', 3)

  # No space after ":" in a member variable declaration
  lines =<< trim END
    vim9script
    class Something
      var count:number
    endclass
  END
  v9.CheckSourceFailure(lines, "E1069: White space required after ':'", 3)

  # Missing ":var" in a "var" member variable declaration (without initialization)
  lines =<< trim END
    vim9script
    class Something
      var: number
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1317: Invalid object variable declaration: var: number', 3)

  # Missing ":var" in a "var" member variable declaration (with initialization)
  lines =<< trim END
    vim9script
    class Something
      var: number = 42
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1317: Invalid object variable declaration: var: number = 42', 3)

  # Missing ":var" in a "var" member variable declaration (type inferred)
  lines =<< trim END
    vim9script
    class Something
      var = 42
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1317: Invalid object variable declaration: var = 42', 3)

  # Test for unsupported comment specifier
  lines =<< trim END
    vim9script
    class Something
      # comment
      #{
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1170: Cannot use #{ to start a comment', 3)

  # Test for using class as a bool
  lines =<< trim END
    vim9script
    class A
    endclass
    if A
    endif
  END
  v9.CheckSourceFailure(lines, 'E1405: Class "A" cannot be used as a value', 4)

  # Test for using object as a bool
  lines =<< trim END
    vim9script
    class A
    endclass
    var a = A.new()
    if a
    endif
  END
  v9.CheckSourceFailure(lines, 'E1320: Using an Object as a Number', 5)

  # Test for using class as a float
  lines =<< trim END
    vim9script
    class A
    endclass
    sort([1.1, A], 'f')
  END
  v9.CheckSourceFailure(lines, 'E1405: Class "A" cannot be used as a value', 4)

  # Test for using object as a float
  lines =<< trim END
    vim9script
    class A
    endclass
    var a = A.new()
    sort([1.1, a], 'f')
  END
  v9.CheckSourceFailure(lines, 'E1322: Using an Object as a Float', 5)

  # Test for using class as a string
  lines =<< trim END
    vim9script
    class A
    endclass
    :exe 'call ' .. A
  END
  v9.CheckSourceFailure(lines, 'E1405: Class "A" cannot be used as a value', 4)

  # Test for using object as a string
  lines =<< trim END
    vim9script
    class A
    endclass
    var a = A.new()
    :exe 'call ' .. a
  END
  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.
  lines =<< trim END
    vim9script

    class TextPosition
      var lnum: number
      var col: number

      # make a nicely formatted string
      def ToString(): string
        return $'({this.lnum}, {this.col})'
      enddef
    endclass

    # use the automatically generated new() method
    var pos = TextPosition.new(2, 12)
    assert_equal(2, pos.lnum)
    assert_equal(12, pos.col)

    # call an object method
    assert_equal('(2, 12)', pos.ToString())

    assert_equal(v:t_class, type(TextPosition))
    assert_equal(v:t_object, type(pos))
    assert_equal('class<TextPosition>', typename(TextPosition))
    assert_equal('object<TextPosition>', typename(pos))
  END
  v9.CheckSourceSuccess(lines)

  # When referencing object methods, space cannot be used after a "."
  lines =<< trim END
    vim9script
    class A
      def Foo(): number
        return 10
      enddef
    endclass
    var a = A.new()
    var v = a. Foo()
  END
  v9.CheckSourceFailure(lines, "E1202: No white space allowed after '.'", 8)

  # Using an object without specifying a method or a member variable
  lines =<< trim END
    vim9script
    class A
      def Foo(): number
        return 10
      enddef
    endclass
    var a = A.new()
    var v = a.
  END
  v9.CheckSourceFailure(lines, 'E15: Invalid expression: "a."', 8)

  # Error when parsing the arguments of an object method.
  lines =<< trim END
    vim9script
    class A
      def Foo()
      enddef
    endclass
    var a = A.new()
    var v = a.Foo(,)
  END
  v9.CheckSourceFailure(lines, 'E15: Invalid expression: "a.Foo(,)"', 7)

  # Use a multi-line initialization for a member variable
  lines =<< trim END
    vim9script
    class A
      var y = {
        X: 1
      }
    endclass
    var a = A.new()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Tests for object/class methods in a class
def Test_class_def_method()
  # Using the "public" keyword when defining an object method
  var lines =<< trim END
    vim9script
    class A
      public def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1388: public keyword not supported for a method', 3)

  # Using the "public" keyword when defining a class method
  lines =<< trim END
    vim9script
    class A
      public static def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1388: public keyword not supported for a method', 3)

  # Using the "public" keyword when defining an object protected method
  lines =<< trim END
    vim9script
    class A
      public def _Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1388: public keyword not supported for a method', 3)

  # Using the "public" keyword when defining a class protected method
  lines =<< trim END
    vim9script
    class A
      public static def _Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1388: public keyword not supported for a method', 3)

  # Using a "def" keyword without an object method name
  lines =<< trim END
    vim9script
    class A
      def
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: def', 3)

  # Using a "def" keyword without a class method name
  lines =<< trim END
    vim9script
    class A
      static def
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: static def', 3)
enddef

def Test_class_defined_twice()
  # class defined twice should fail
  var lines =<< trim END
    vim9script
    class There
    endclass
    class There
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "There"', 4)

  # one class, reload same script twice is OK
  lines =<< trim END
    vim9script
    class There
    endclass
  END
  writefile(lines, 'XclassTwice.vim', 'D')
  source XclassTwice.vim
  source XclassTwice.vim
enddef

def Test_returning_null_object()
  # this was causing an internal error
  var lines =<< trim END
    vim9script

    class BufferList
      def Current(): any
        return null_object
      enddef
    endclass

    var buffers = BufferList.new()
    echo buffers.Current()
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_using_null_class()
  var lines =<< trim END
    @_ = null_class.member
  END
  v9.CheckDefExecAndScriptFailure(lines, ['E1395: Using a null class', 'E1363: Incomplete type'])

  # Test for using a null class as a value
  lines =<< trim END
    vim9script
    echo empty(null_class)
  END
  v9.CheckSourceFailure(lines, 'E1405: Class "" cannot be used as a value', 2)

  # Test for using a null class with string()
  lines =<< trim END
    vim9script
    assert_equal('class [unknown]', string(null_class))
  END
  v9.CheckSourceSuccess(lines)

  # Test for using a null class with type() and typename()
  lines =<< trim END
    vim9script
    assert_equal(12, type(null_class))
    assert_equal('class<Unknown>', typename(null_class))
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_class_interface_wrong_end()
  var lines =<< trim END
    vim9script
    abstract class SomeName
      var member = 'text'
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E476: Invalid command: endinterface, expected endclass', 4)

  lines =<< trim END
    vim9script
    export interface AnotherName
      var member: string
    endclass
  END
  v9.CheckSourceFailure(lines, 'E476: Invalid command: endclass, expected endinterface', 4)
enddef

def Test_object_not_set()
  # Use an uninitialized object in script context
  var lines =<< trim END
    vim9script

    class State
      var value = 'xyz'
    endclass

    var state: State
    var db = {'xyz': 789}
    echo db[state.value]
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 9)

  # Use an uninitialized object from a def function
  lines =<< trim END
    vim9script

    class Class
      var id: string
      def Method1()
        echo 'Method1' .. this.id
      enddef
    endclass

    var obj: Class
    def Func()
      obj.Method1()
    enddef
    Func()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 1)

  # Pass an uninitialized object variable to a "new" function and try to call an
  # object method.
  lines =<< trim END
    vim9script

    class Background
      var background = 'dark'
    endclass

    class Colorscheme
      var _bg: Background

      def GetBackground(): string
        return this._bg.background
      enddef
    endclass

    var bg: Background           # UNINITIALIZED
    echo Colorscheme.new(bg).GetBackground()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 1)

  # TODO: this should not give an error but be handled at runtime
  lines =<< trim END
    vim9script

    class Class
      var id: string
      def Method1()
        echo 'Method1' .. this.id
      enddef
    endclass

    var obj = null_object
    def Func()
      obj.Method1()
    enddef
    Func()
  END
  v9.CheckSourceFailure(lines, 'E1363: Incomplete type', 1)

  # Reference a object variable through a null class object which is stored in a
  # variable of type "any".
  lines =<< trim END
    vim9script

    def Z()
      var o: any = null_object
      o.v = 4
    enddef
    Z()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 2)

  # Do "echom" of a null object variable.
  lines =<< trim END
    vim9script

    def X()
      var x = null_object
      echom x
    enddef
    X()
  END
  v9.CheckSourceFailure(lines, 'E1324: Using an Object as a String', 2)

  # Use a null object variable that vim wants to force to number.
  lines =<< trim END
    vim9script

    def X()
      var o = null_object
      var l = [ 1, o]
      sort(l, 'N')
    enddef
    X()
  END
  v9.CheckSourceFailure(lines, 'E1324: Using an Object as a String', 3)
enddef

" Null object assignment and comparison
def Test_null_object_assign_compare()
  var lines =<< trim END
    vim9script

    var nullo = null_object
    def F(): any
      return nullo
    enddef
    assert_equal('object<Unknown>', typename(F()))

    var o0 = F()
    assert_true(o0 == null_object)
    assert_true(o0 == null)

    var o1: any = nullo
    assert_true(o1 == null_object)
    assert_true(o1 == null)

    def G()
      var x = null_object
    enddef

    class C
    endclass
    var o2: C
    assert_true(o2 == null_object)
    assert_true(o2 == null)

    o2 = null_object
    assert_true(o2 == null)

    o2 = C.new()
    assert_true(o2 != null)

    o2 = null_object
    assert_true(o2 == null)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for object member initialization and disassembly
def Test_class_member_initializer()
  var lines =<< trim END
    vim9script

    class TextPosition
      var lnum: number = 1
      var col: number = 1

      # constructor with only the line number
      def new(lnum: number)
        this.lnum = lnum
      enddef
    endclass

    var pos = TextPosition.new(3)
    assert_equal(3, pos.lnum)
    assert_equal(1, pos.col)

    var instr = execute('disassemble TextPosition.new')
    assert_match('new\_s*' ..
          '0 NEW TextPosition size \d\+\_s*' ..
          '\d PUSHNR 1\_s*' ..
          '\d STORE_THIS 0\_s*' ..
          '\d PUSHNR 1\_s*' ..
          '\d STORE_THIS 1\_s*' ..
          'this.lnum = lnum\_s*' ..
          '\d LOAD arg\[-1]\_s*' ..
          '\d PUSHNR 0\_s*' ..
          '\d LOAD $0\_s*' ..
          '\d\+ STOREINDEX object\_s*' ..
          '\d\+ RETURN object.*',
          instr)
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_member_any_used_as_object()
  var lines =<< trim END
    vim9script

    class Inner
      public var value: number = 0
    endclass

    class Outer
      var inner: any
    endclass

    def F(outer: Outer)
      outer.inner.value = 1
    enddef

    var inner_obj = Inner.new(0)
    var outer_obj = Outer.new(inner_obj)
    F(outer_obj)
    assert_equal(1, inner_obj.value)
  END
  v9.CheckSourceSuccess(lines)

  # Try modifying a protected variable using an "any" object
  lines =<< trim END
    vim9script

    class Inner
      var _value: string = ''
    endclass

    class Outer
      var inner: any
    endclass

    def F(outer: Outer)
      outer.inner._value = 'b'
    enddef

    var inner_obj = Inner.new('a')
    var outer_obj = Outer.new(inner_obj)
    F(outer_obj)
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_value" in class "Inner"', 1)

  # Try modifying a non-existing variable using an "any" object
  lines =<< trim END
    vim9script

    class Inner
      var value: string = ''
    endclass

    class Outer
      var inner: any
    endclass

    def F(outer: Outer)
      outer.inner.someval = 'b'
    enddef

    var inner_obj = Inner.new('a')
    var outer_obj = Outer.new(inner_obj)
    F(outer_obj)
  END
  v9.CheckSourceFailure(lines, 'E1326: Variable "someval" not found in object "Inner"', 1)
enddef

" Nested assignment to a object variable which is of another class type
def Test_assignment_nested_type()
  var lines =<< trim END
    vim9script

    class Inner
      public var value: number = 0
    endclass

    class Outer
      var inner: Inner
    endclass

    def F(outer: Outer)
      outer.inner.value = 1
    enddef

    def Test_assign_to_nested_typed_member()
      var inner = Inner.new(0)
      var outer = Outer.new(inner)
      F(outer)
      assert_equal(1, inner.value)
    enddef

    Test_assign_to_nested_typed_member()

    var script_inner = Inner.new(0)
    var script_outer = Outer.new(script_inner)
    script_outer.inner.value = 1
    assert_equal(1, script_inner.value)
  END
  v9.CheckSourceSuccess(lines)

  # Assignment where target item is read only in :def
  lines =<< trim END
    vim9script

    class Inner
      var value: number = 0
    endclass

    class Outer
      var inner: Inner
    endclass

    def F(outer: Outer)
      outer.inner.value = 1
    enddef

    def Test_assign_to_nested_typed_member()
      var inner = Inner.new(0)
      var outer = Outer.new(inner)
      F(outer)
      assert_equal(1, inner.value)
    enddef

    Test_assign_to_nested_typed_member()
  END
  v9.CheckSourceFailure(lines, 'E1335: Variable "value" in class "Inner" is not writable', 1)

  # Assignment where target item is read only script level
  lines =<< trim END
    vim9script

    class Inner
      var value: number = 0
    endclass

    class Outer
      var inner: Inner
    endclass

    def F(outer: Outer)
      outer.inner.value = 1
    enddef

    var script_inner = Inner.new(0)
    var script_outer = Outer.new(script_inner)
    script_outer.inner.value = 1
    assert_equal(1, script_inner.value)
  END
  v9.CheckSourceFailure(lines, 'E1335: Variable "value" in class "Inner" is not writable', 17)
enddef

def Test_assignment_with_operator()
  # Use "+=" to assign to a object variable
  var lines =<< trim END
    vim9script

    class Foo
      public var x: number

      def Add(n: number)
        this.x += n
      enddef
    endclass

    var f =  Foo.new(3)
    f.Add(17)
    assert_equal(20, f.x)

    def AddToFoo(obj: Foo)
      obj.x += 3
    enddef

    AddToFoo(f)
    assert_equal(23, f.x)
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_list_of_objects()
  var lines =<< trim END
    vim9script

    class Foo
      def Add()
      enddef
    endclass

    def ProcessList(fooList: list<Foo>)
      for foo in fooList
        foo.Add()
      endfor
    enddef

    var l: list<Foo> = [Foo.new()]
    ProcessList(l)
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_expr_after_using_object()
  var lines =<< trim END
    vim9script

    class Something
      var label: string = ''
    endclass

    def Foo(): Something
      var v = Something.new()
      echo 'in Foo(): ' .. typename(v)
      return v
    enddef

    Foo()
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_class_default_new()
  var lines =<< trim END
    vim9script

    class TextPosition
      var lnum: number = 1
      var col: number = 1
    endclass

    var pos = TextPosition.new()
    assert_equal(1, pos.lnum)
    assert_equal(1, pos.col)

    pos = TextPosition.new(v:none, v:none)
    assert_equal(1, pos.lnum)
    assert_equal(1, pos.col)

    pos = TextPosition.new(3, 22)
    assert_equal(3, pos.lnum)
    assert_equal(22, pos.col)

    pos = TextPosition.new(v:none, 33)
    assert_equal(1, pos.lnum)
    assert_equal(33, pos.col)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Person
      var name: string
      var age: number = 42
      var education: string = "unknown"

      def new(this.name, this.age = v:none, this.education = v:none)
      enddef
    endclass

    var piet = Person.new("Piet")
    assert_equal("Piet", piet.name)
    assert_equal(42, piet.age)
    assert_equal("unknown", piet.education)

    var chris = Person.new("Chris", 4, "none")
    assert_equal("Chris", chris.name)
    assert_equal(4, chris.age)
    assert_equal("none", chris.education)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Person
      var name: string
      var age: number = 42
      var education: string = "unknown"

      def new(this.name, this.age = v:none, this.education = v:none)
      enddef
    endclass

    var missing = Person.new()
  END
  v9.CheckSourceFailure(lines, 'E119: Not enough arguments for function: new', 11)

  # Using a specific value to initialize an instance variable in the new()
  # method.
  lines =<< trim END
    vim9script
    class A
      var val: string
      def new(this.val = 'a')
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, "E1328: Constructor default value must be v:none:  = 'a'", 4)
enddef

def Test_class_new_with_object_member()
  var lines =<< trim END
    vim9script

    class C
      var str: string
      var num: number
      def new(this.str, this.num)
      enddef
      def newVals(this.str, this.num)
      enddef
    endclass

    def Check()
      try
        var c = C.new('cats', 2)
        assert_equal('cats', c.str)
        assert_equal(2, c.num)

        c = C.newVals('dogs', 4)
        assert_equal('dogs', c.str)
        assert_equal(4, c.num)
      catch
        assert_report($'Unexpected exception was caught: {v:exception}')
      endtry
    enddef

    Check()
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class C
      var str: string
      var num: number
      def new(this.str, this.num)
      enddef
    endclass

    def Check()
      try
        var c = C.new(1, 2)
      catch
        assert_report($'Unexpected exception was caught: {v:exception}')
      endtry
    enddef

    Check()
  END
  v9.CheckSourceFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number', 2)

  lines =<< trim END
    vim9script

    class C
      var str: string
      var num: number
      def newVals(this.str, this.num)
      enddef
    endclass

    def Check()
      try
        var c = C.newVals('dogs', 'apes')
      catch
        assert_report($'Unexpected exception was caught: {v:exception}')
      endtry
    enddef

    Check()
  END
  v9.CheckSourceFailure(lines, 'E1013: Argument 2: type mismatch, expected number but got string', 2)

  lines =<< trim END
    vim9script

    class C
      var str: string
      def new(str: any)
      enddef
    endclass

    def Check()
      try
        var c = C.new(1)
      catch
        assert_report($'Unexpected exception was caught: {v:exception}')
      endtry
    enddef

    Check()
  END
  v9.CheckSourceSuccess(lines)

  # Try using "this." argument in a class method
  lines =<< trim END
    vim9script
    class A
      var val = 10
      static def Foo(this.val: number)
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1390: Cannot use an object variable "this.val" except with the "new" method', 4)

  # Try using "this." argument in an object method
  lines =<< trim END
    vim9script
    class A
      var val = 10
      def Foo(this.val: number)
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1390: Cannot use an object variable "this.val" except with the "new" method', 4)
enddef

def Test_class_object_member_inits()
  var lines =<< trim END
    vim9script
    class TextPosition
      var lnum: number
      var col = 1
      var addcol: number = 2
    endclass

    var pos = TextPosition.new()
    assert_equal(0, pos.lnum)
    assert_equal(1, pos.col)
    assert_equal(2, pos.addcol)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class TextPosition
      var lnum
      var col = 1
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)

  # If the type is not specified for a member, then it should be set during
  # object creation and not when defining the class.
  lines =<< trim END
    vim9script

    var init_count = 0
    def Init(): string
      init_count += 1
      return 'foo'
    enddef

    class A
      var str1 = Init()
      var str2: string = Init()
      var col = 1
    endclass

    assert_equal(init_count, 0)
    var a = A.new()
    assert_equal(init_count, 2)
  END
  v9.CheckSourceSuccess(lines)

  # Test for initializing an object member with an unknown variable/type
  lines =<< trim END
    vim9script
    class A
       var value = init_val
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1001: Variable not found: init_val', 1)

  # Test for initializing an object member with an special type
  lines =<< trim END
    vim9script
    class A
       var value: void
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1330: Invalid type for object variable: void', 3)
enddef

" Test for instance variable access
def Test_instance_variable_access()
  var lines =<< trim END
    vim9script
    class Triple
       var _one = 1
       var two = 2
       public var three = 3

       def GetOne(): number
         return this._one
       enddef
    endclass

    var trip = Triple.new()
    assert_equal(1, trip.GetOne())
    assert_equal(2, trip.two)
    assert_equal(3, trip.three)
    assert_fails('echo trip._one', 'E1333: Cannot access protected variable "_one" in class "Triple"')

    assert_fails('trip._one = 11', 'E1333: Cannot access protected variable "_one" in class "Triple"')
    assert_fails('trip.two = 22', 'E1335: Variable "two" in class "Triple" is not writable')
    trip.three = 33
    assert_equal(33, trip.three)

    assert_fails('trip.four = 4', 'E1326: Variable "four" not found in object "Triple"')
  END
  v9.CheckSourceSuccess(lines)

  # Test for a public member variable name beginning with an underscore
  lines =<< trim END
    vim9script
    class A
      public var _val = 10
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1332: public variable name cannot start with underscore: public var _val = 10', 3)

  lines =<< trim END
    vim9script

    class MyCar
      var make: string
      var age = 5

      def new(make_arg: string)
        this.make = make_arg
      enddef

      def GetMake(): string
        return $"make = {this.make}"
      enddef
      def GetAge(): number
        return this.age
      enddef
    endclass

    var c = MyCar.new("abc")
    assert_equal('make = abc', c.GetMake())

    c = MyCar.new("def")
    assert_equal('make = def', c.GetMake())

    var c2 = MyCar.new("123")
    assert_equal('make = 123', c2.GetMake())

    def CheckCar()
      assert_equal("make = def", c.GetMake())
      assert_equal(5, c.GetAge())
    enddef
    CheckCar()
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class MyCar
      var make: string

      def new(make_arg: string)
        this.make = make_arg
      enddef
    endclass

    var c = MyCar.new("abc")
    var c = MyCar.new("def")
  END
  v9.CheckSourceFailure(lines, 'E1041: Redefining script item: "c"', 12)

  lines =<< trim END
    vim9script

    class Foo
      var x: list<number> = []

      def Add(n: number): any
        this.x->add(n)
        return this
      enddef
    endclass

    echo Foo.new().Add(1).Add(2).x
    echo Foo.new().Add(1).Add(2)
          .x
    echo Foo.new().Add(1)
          .Add(2).x
    echo Foo.new()
          .Add(1).Add(2).x
    echo Foo.new()
          .Add(1) 
          .Add(2)
          .x
  END
  v9.CheckSourceSuccess(lines)

  # Test for "public" cannot be abbreviated
  lines =<< trim END
    vim9script
    class Something
      pub var val = 1
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: pub var val = 1', 3)

  # Test for "public" keyword must be followed by "var" or "static".
  lines =<< trim END
    vim9script
    class Something
      public val = 1
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1331: public must be followed by "var" or "static"', 3)

  # Modify a instance variable using the class name in the script context
  lines =<< trim END
    vim9script
    class A
      public var val = 1
    endclass
    A.val = 1
  END
  v9.CheckSourceFailure(lines, 'E1376: Object variable "val" accessible only using class "A" object', 5)

  # Read a instance variable using the class name in the script context
  lines =<< trim END
    vim9script
    class A
      public var val = 1
    endclass
    var i = A.val
  END
  v9.CheckSourceFailure(lines, 'E1376: Object variable "val" accessible only using class "A" object', 5)

  # Modify a instance variable using the class name in a def function
  lines =<< trim END
    vim9script
    class A
      public var val = 1
    endclass
    def T()
      A.val = 1
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1376: Object variable "val" accessible only using class "A" object', 1)

  # Read a instance variable using the class name in a def function
  lines =<< trim END
    vim9script
    class A
      public var val = 1
    endclass
    def T()
      var i = A.val
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1376: Object variable "val" accessible only using class "A" object', 1)

  # Access from child class extending a class:
  lines =<< trim END
    vim9script
    class A
      var ro_obj_var = 10
      public var rw_obj_var = 20
      var _priv_obj_var = 30
    endclass

    class B extends A
      def Foo()
        var x: number
        x = this.ro_obj_var
        this.ro_obj_var = 0
        x = this.rw_obj_var
        this.rw_obj_var = 0
        x = this._priv_obj_var
        this._priv_obj_var = 0
      enddef
    endclass

    var b = B.new()
    b.Foo()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for class variable access
def Test_class_variable_access()
  # Test for "static" cannot be abbreviated
  var lines =<< trim END
    vim9script
    class Something
      stat var val = 1
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: stat var val = 1', 3)

  # Test for "static" cannot be followed by "public".
  lines =<< trim END
    vim9script
    class Something
      static public var val = 1
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1368: Static must be followed by "var" or "def"', 3)

  # A readonly class variable cannot be modified from a child class
  lines =<< trim END
    vim9script
    class A
      static var ro_class_var = 40
    endclass

    class B extends A
      def Foo()
        A.ro_class_var = 50
      enddef
    endclass

    var b = B.new()
    b.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1335: Variable "ro_class_var" in class "A" is not writable', 1)

  # A protected class variable cannot be accessed from a child class
  lines =<< trim END
    vim9script
    class A
      static var _priv_class_var = 60
    endclass

    class B extends A
      def Foo()
        var i = A._priv_class_var
      enddef
    endclass

    var b = B.new()
    b.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_priv_class_var" in class "A"', 1)

  # A protected class variable cannot be modified from a child class
  lines =<< trim END
    vim9script
    class A
      static var _priv_class_var = 60
    endclass

    class B extends A
      def Foo()
        A._priv_class_var = 0
      enddef
    endclass

    var b = B.new()
    b.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_priv_class_var" in class "A"', 1)

  # Access from child class extending a class and from script context
  lines =<< trim END
    vim9script
    class A
      static var ro_class_var = 10
      public static var rw_class_var = 20
      static var _priv_class_var = 30
    endclass

    class B extends A
      def Foo()
        var x: number
        x = A.ro_class_var
        assert_equal(10, x)
        x = A.rw_class_var
        assert_equal(25, x)
        A.rw_class_var = 20
        assert_equal(20, A.rw_class_var)
      enddef
    endclass

    assert_equal(10, A.ro_class_var)
    assert_equal(20, A.rw_class_var)
    A.rw_class_var = 25
    assert_equal(25, A.rw_class_var)
    var b = B.new()
    b.Foo()
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_class_object_compare()
  var class_lines =<< trim END
    vim9script
    class Item
      var nr = 0
      var name = 'xx'
    endclass
  END

  # used at the script level and in a compiled function
  var test_lines =<< trim END
    var i1 = Item.new()
    assert_equal(i1, i1)
    assert_true(i1 is i1)
    var i2 = Item.new()
    assert_equal(i1, i2)
    assert_false(i1 is i2)
    var i3 = Item.new(0, 'xx')
    assert_equal(i1, i3)

    var io1 = Item.new(1, 'xx')
    assert_notequal(i1, io1)
    var io2 = Item.new(0, 'yy')
    assert_notequal(i1, io2)
  END

  v9.CheckSourceSuccess(class_lines + test_lines)
  v9.CheckSourceSuccess(
    class_lines + ['def Test()'] + test_lines + ['enddef', 'Test()'])

  for op in ['>', '>=', '<', '<=', '=~', '!~']
    var op_lines = [
          'var i1 = Item.new()',
          'var i2 = Item.new()',
          'echo i1 ' .. op .. ' i2',
          ]
    v9.CheckSourceFailure(class_lines + op_lines, 'E1153: Invalid operation for object', 8)
    v9.CheckSourceFailure(class_lines
          + ['def Test()'] + op_lines + ['enddef', 'Test()'], 'E1153: Invalid operation for object')
  endfor
enddef

def Test_object_type()
  var lines =<< trim END
    vim9script

    class One
      var one = 1
    endclass
    class Two
      var two = 2
    endclass
    class TwoMore extends Two
      var more = 9
    endclass

    var o: One = One.new()
    var t: Two = Two.new()
    var m: TwoMore = TwoMore.new()
    var tm: Two = TwoMore.new()

    t = m
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class One
      var one = 1
    endclass
    class Two
      var two = 2
    endclass

    var o: One = Two.new()
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected object<One> but got object<Two>', 10)

  lines =<< trim END
    vim9script

    interface One
      def GetMember(): number
    endinterface
    class Two implements One
      var one = 1
      def GetMember(): number
        return this.one
      enddef
    endclass

    var o: One = Two.new(5)
    assert_equal(5, o.GetMember())
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class Num
      var n: number = 0
    endclass

    def Ref(name: string): func(Num): Num
      return (arg: Num): Num => {
        return eval(name)(arg)
      }
    enddef

    const Fn = Ref('Double')
    var Double = (m: Num): Num => Num.new(m.n * 2)

    echo Fn(Num.new(4))
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_class_member()
  # check access rules
  var lines =<< trim END
    vim9script
    class TextPos
       var lnum = 1
       var col = 1
       static var counter = 0
       static var _secret = 7
       public static var anybody = 42

       static def AddToCounter(nr: number)
         counter += nr
       enddef
    endclass

    assert_equal(0, TextPos.counter)
    TextPos.AddToCounter(3)
    assert_equal(3, TextPos.counter)
    assert_fails('echo TextPos.noSuchMember', 'E1337: Class variable "noSuchMember" not found in class "TextPos"')

    def GetCounter(): number
      return TextPos.counter
    enddef
    assert_equal(3, GetCounter())

    assert_fails('TextPos.noSuchMember = 2', 'E1337: Class variable "noSuchMember" not found in class "TextPos"')
    assert_fails('TextPos.counter = 5', 'E1335: Variable "counter" in class "TextPos" is not writable')
    assert_fails('TextPos.counter += 5', 'E1335: Variable "counter" in class "TextPos" is not writable')

    assert_fails('echo TextPos._secret', 'E1333: Cannot access protected variable "_secret" in class "TextPos"')
    assert_fails('TextPos._secret = 8', 'E1333: Cannot access protected variable "_secret" in class "TextPos"')

    assert_equal(42, TextPos.anybody)
    TextPos.anybody = 12
    assert_equal(12, TextPos.anybody)
    TextPos.anybody += 5
    assert_equal(17, TextPos.anybody)
  END
  v9.CheckSourceSuccess(lines)

  # example in the help
  lines =<< trim END
    vim9script
    class OtherThing
      var size: number
      static var totalSize: number

      def new(this.size)
        totalSize += this.size
      enddef
    endclass
    assert_equal(0, OtherThing.totalSize)
    var to3 = OtherThing.new(3)
    assert_equal(3, OtherThing.totalSize)
    var to7 = OtherThing.new(7)
    assert_equal(10, OtherThing.totalSize)
  END
  v9.CheckSourceSuccess(lines)

  # using static class member twice
  lines =<< trim END
    vim9script

    class HTML
      static var author: string = 'John Doe'

      static def MacroSubstitute(s: string): string
        return substitute(s, '{{author}}', author, 'gi')
      enddef
    endclass

    assert_equal('some text', HTML.MacroSubstitute('some text'))
    assert_equal('some text', HTML.MacroSubstitute('some text'))
  END
  v9.CheckSourceSuccess(lines)

  # access protected member in lambda
  lines =<< trim END
    vim9script

    class Foo
      var _x: number = 0

      def Add(n: number): number
        const F = (): number => this._x + n
        return F()
      enddef
    endclass

    var foo = Foo.new()
    assert_equal(5, foo.Add(5))
  END
  v9.CheckSourceSuccess(lines)

  # access protected member in lambda body
  lines =<< trim END
    vim9script

    class Foo
      var _x: number = 6

      def Add(n: number): number
        var Lam = () => {
          this._x = this._x + n
        }
        Lam()
        return this._x
      enddef
    endclass

    var foo = Foo.new()
    assert_equal(13, foo.Add(7))
  END
  v9.CheckSourceSuccess(lines)

  # check shadowing
  lines =<< trim END
    vim9script

    class Some
      static var count = 0
      def Method(count: number)
        echo count
      enddef
    endclass

    var s = Some.new()
    s.Method(7)
  END
  v9.CheckSourceFailure(lines, 'E1340: Argument already declared in the class: count', 5)

  # Use a local variable in a method with the same name as a class variable
  lines =<< trim END
    vim9script

    class Some
      static var count = 0
      def Method(arg: number)
        var count = 3
        echo arg count
      enddef
    endclass

    var s = Some.new()
    s.Method(7)
  END
  v9.CheckSourceFailure(lines, 'E1341: Variable already declared in the class: count', 1)

  # Test for using an invalid type for a member variable
  lines =<< trim END
    vim9script
    class A
      var val: xxx
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1010: Type not recognized: xxx', 3)

  # Test for setting a member on a null object
  lines =<< trim END
    vim9script
    class A
      public var val: string
    endclass

    def F()
      var obj: A
      obj.val = ""
    enddef
    F()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 2)

  # Test for accessing a member on a null object
  lines =<< trim END
    vim9script
    class A
      var val: string
    endclass

    def F()
      var obj: A
      echo obj.val
    enddef
    F()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 2)

  # Test for setting a member on a null object, at script level
  lines =<< trim END
    vim9script
    class A
      public var val: string
    endclass

    var obj: A
    obj.val = ""
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 7)

  # Test for accessing a member on a null object, at script level
  lines =<< trim END
    vim9script
    class A
      var val: string
    endclass

    var obj: A
    echo obj.val
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 7)

  # Test for no space before or after the '=' when initializing a member
  # variable
  lines =<< trim END
    vim9script
    class A
      var val: number= 10
    endclass
  END
  v9.CheckSourceFailure(lines, "E1004: White space required before and after '='", 3)
  lines =<< trim END
    vim9script
    class A
      var val: number =10
    endclass
  END
  v9.CheckSourceFailure(lines, "E1004: White space required before and after '='", 3)

  # Access a non-existing member
  lines =<< trim END
    vim9script
    class A
    endclass
    var a = A.new()
    var v = a.bar
  END
  v9.CheckSourceFailure(lines, 'E1326: Variable "bar" not found in object "A"', 5)
enddef

" These messages should show the defining class of the variable (base class),
" not the class that did the reference (super class)
def Test_defining_class_message()
  var lines =<< trim END
    vim9script

    class Base
      var _v1: list<list<number>>
    endclass

    class Child extends Base
    endclass

    var o = Child.new()
    var x = o._v1
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_v1" in class "Base"', 11)
  lines =<< trim END
    vim9script

    class Base
      var _v1: list<list<number>>
    endclass

    class Child extends Base
    endclass

    def F()
      var o = Child.new()
      var x = o._v1
    enddef
    F()
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_v1" in class "Base"', 2)
  lines =<< trim END
    vim9script

    class Base
      var v1: list<list<number>>
    endclass

    class Child extends Base
    endclass

    var o = Child.new()
    o.v1 = []
  END
  v9.CheckSourceFailure(lines, 'E1335: Variable "v1" in class "Base" is not writable', 11)
  lines =<< trim END
    vim9script

    class Base
      var v1: list<list<number>>
    endclass

    class Child extends Base
    endclass

    def F()
      var o = Child.new()
      o.v1 = []
    enddef
    F()
  END

  # Attempt to read a protected variable that is in the middle
  # of the class hierarchy.
  v9.CheckSourceFailure(lines, 'E1335: Variable "v1" in class "Base" is not writable', 2)
  lines =<< trim END
    vim9script

    class Base0
    endclass

    class Base extends Base0
      var _v1: list<list<number>>
    endclass

    class Child extends Base
    endclass

    def F()
      var o = Child.new()
      var x = o._v1
    enddef
    F()
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_v1" in class "Base"', 2)

  # Attempt to read a protected variable that is at the start
  # of the class hierarchy.
  lines =<< trim END
    vim9script

    class Base0
    endclass

    class Base extends Base0
    endclass

    class Child extends Base
      var _v1: list<list<number>>
    endclass

    def F()
      var o = Child.new()
      var x = o._v1
    enddef
    F()
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_v1" in class "Child"', 2)
enddef

func Test_class_garbagecollect()
  let lines =<< trim END
    vim9script

    class Point
      var p = [2, 3]
      static var pl = ['a', 'b']
      static var pd = {a: 'a', b: 'b'}
    endclass

    echo Point.pl Point.pd
    call test_garbagecollect_now()
    echo Point.pl Point.pd
  END
  call v9.CheckSourceSuccess(lines)

  let lines =<< trim END
    vim9script

    interface View
    endinterface

    class Widget
      var view: View
    endclass

    class MyView implements View
      var widget: Widget

      def new()
        # this will result in a circular reference to this object
        var widget = Widget.new(this)
      enddef
    endclass

    var view = MyView.new()

    # overwrite "view", will be garbage-collected next
    view = MyView.new()
    test_garbagecollect_now()
  END
  call v9.CheckSourceSuccess(lines)
endfunc

" Test interface garbage collection
func Test_interface_garbagecollect()
  let lines =<< trim END
    vim9script

    interface I
      var ro_obj_var: number

      def ObjFoo(): number
    endinterface

    class A implements I
      static var ro_class_var: number = 10
      public static var rw_class_var: number = 20
      static var _priv_class_var: number = 30
      var ro_obj_var: number = 40
      var _priv_obj_var: number = 60

      static def _ClassBar(): number
        return _priv_class_var
      enddef

      static def ClassFoo(): number
        return ro_class_var + rw_class_var + A._ClassBar()
      enddef

      def _ObjBar(): number
        return this._priv_obj_var
      enddef

      def ObjFoo(): number
        return this.ro_obj_var + this._ObjBar()
      enddef
    endclass

    assert_equal(60, A.ClassFoo())
    var o = A.new()
    assert_equal(100, o.ObjFoo())
    test_garbagecollect_now()
    assert_equal(60, A.ClassFoo())
    assert_equal(100, o.ObjFoo())
  END
  call v9.CheckSourceSuccess(lines)
endfunc

def Test_class_method()
  var lines =<< trim END
    vim9script
    class Value
      var value = 0
      static var objects = 0

      def new(v: number)
        this.value = v
        ++objects
      enddef

      static def GetCount(): number
        return objects
      enddef
    endclass

    assert_equal(0, Value.GetCount())
    var v1 = Value.new(2)
    assert_equal(1, Value.GetCount())
    var v2 = Value.new(7)
    assert_equal(2, Value.GetCount())
  END
  v9.CheckSourceSuccess(lines)

  # Test for cleaning up after a class definition failure when using class
  # functions.
  lines =<< trim END
    vim9script
    class A
      static def Foo()
      enddef
      aaa
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: aaa', 5)

  # Test for calling a class method from another class method without the class
  # name prefix.
  lines =<< trim END
    vim9script
    class A
      static var myList: list<number> = [1]
      static def Foo(n: number)
        myList->add(n)
      enddef
      static def Bar()
        Foo(2)
      enddef
      def Baz()
        Foo(3)
      enddef
    endclass
    A.Bar()
    var a = A.new()
    a.Baz()
    assert_equal([1, 2, 3], A.myList)
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_class_defcompile()
  var lines =<< trim END
    vim9script

    class C
      def Fo(i: number): string
        return i
      enddef
    endclass

    defcompile C.Fo
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected string but got number', 1)

  lines =<< trim END
    vim9script

    class C
      static def Fc(): number
        return 'x'
      enddef
    endclass

    defcompile C.Fc
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected number but got string', 1)

  lines =<< trim END
    vim9script

    class C
      static def new()
      enddef
    endclass

    defcompile C.new
  END
  v9.CheckSourceFailure(lines, 'E1370: Cannot define a "new" method as static', 5)

  # Trying to compile a function using a non-existing class variable
  lines =<< trim END
    vim9script
    defcompile x.Foo()
  END
  v9.CheckSourceFailure(lines, 'E475: Invalid argument: x.Foo()', 2)

  # Trying to compile a function using a variable which is not a class
  lines =<< trim END
    vim9script
    var x: number
    defcompile x.Foo()
  END
  v9.CheckSourceFailure(lines, 'E475: Invalid argument: x.Foo()', 3)

  # Trying to compile a function without specifying the name
  lines =<< trim END
    vim9script
    class A
    endclass
    defcompile A.
  END
  v9.CheckSourceFailure(lines, 'E475: Invalid argument: A.', 4)

  # Trying to compile a non-existing class object member function
  lines =<< trim END
    vim9script
    class A
    endclass
    var a = A.new()
    defcompile a.Foo()
  END
  v9.CheckSourceFailureList(lines, ['E1326: Variable "Foo" not found in object "A"', 'E475: Invalid argument: a.Foo()'])
enddef

def Test_class_object_to_string()
  var lines =<< trim END
    vim9script
    class TextPosition
      var lnum = 1
      var col = 22
    endclass

    assert_equal("class TextPosition", string(TextPosition))

    var pos = TextPosition.new()
    assert_equal("object of TextPosition {lnum: 1, col: 22}", string(pos))
  END
  v9.CheckSourceSuccess(lines)

  # check string() with object nesting
  lines =<< trim END
    vim9script
    class C
        var nest1: C
        var nest2: C
        def Init(n1: C, n2: C)
            this.nest1 = n1
            this.nest2 = n2
        enddef
    endclass

    var o1 = C.new()
    var o2 = C.new()
    o1.Init(o1, o2)
    o2.Init(o2, o1)

    # The following previously put's vim into an infinite loop.

    var expect = "object of C {nest1: object of C {...}, nest2: object of C {nest1: object of C {...}, nest2: object of C {...}}}"
    assert_equal(expect, string(o1))
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class B
    endclass

    class C
        var b: B
        var c: C
    endclass

    var o1 = C.new(B.new(), C.new(B.new()))
    var expect = "object of C {b: object of B {}, c: object of C {b: object of B {}, c: object of [unknown]}}"
    assert_equal(expect, string(o1))
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_interface_basics()
  var lines =<< trim END
    vim9script
    interface Something
      var ro_var: list<number>
      def GetCount(): number
    endinterface
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    interface SomethingWrong
      static var count = 7
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1342: Interface can only be defined in Vim9 script', 1)

  lines =<< trim END
    vim9script

    interface Some
      var value: number
      def Method(value: number)
    endinterface
  END
  # The argument name and the object member name are the same, but this is not a
  # problem because object members are always accessed with the "this." prefix.
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    interface somethingWrong
      static var count = 7
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1343: Interface name must start with an uppercase letter: somethingWrong', 2)

  lines =<< trim END
    vim9script
    interface SomethingWrong
      var value: string
      var count = 7
      def GetCount(): number
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1344: Cannot initialize a variable in an interface', 4)

  lines =<< trim END
    vim9script
    interface SomethingWrong
      var value: string
      var count: number
      def GetCount(): number
        return 5
      enddef
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1345: Not a valid command in an interface: return 5', 6)

  # Test for "interface" cannot be abbreviated
  lines =<< trim END
    vim9script
    inte Something
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: inte Something', 2)

  # Test for "endinterface" cannot be abbreviated
  lines =<< trim END
    vim9script
    interface Something
    endin
  END
  v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: endin', 3)

  # Additional commands after "interface name"
  lines =<< trim END
    vim9script
    interface Something | var x = 10 | var y = 20
    endinterface
  END
  v9.CheckSourceFailure(lines, "E488: Trailing characters: | var x = 10", 2)

  lines =<< trim END
    vim9script
    export interface EnterExit
      def Enter(): void
      def Exit(): void
    endinterface
  END
  writefile(lines, 'XdefIntf.vim', 'D')

  lines =<< trim END
    vim9script
    import './XdefIntf.vim' as defIntf
    export def With(ee: defIntf.EnterExit, F: func)
      ee.Enter()
      try
        F()
      finally
        ee.Exit()
      endtry
    enddef
  END
  v9.CheckScriptSuccess(lines)

  var imported =<< trim END
    vim9script
    export abstract class EnterExit
      def Enter(): void
      enddef
      def Exit(): void
      enddef
    endclass
  END
  writefile(imported, 'XdefIntf2.vim', 'D')

  lines[1] = " import './XdefIntf2.vim' as defIntf"
  v9.CheckScriptSuccess(lines)
enddef

" Test for using string() with an interface
def Test_interface_to_string()
  var lines =<< trim END
    vim9script
    interface Intf
      def Method(nr: number)
    endinterface
    assert_equal("interface Intf", string(Intf))
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_class_implements_interface()
  var lines =<< trim END
    vim9script

    interface Some
      var count: number
      def Method(nr: number)
    endinterface

    class SomeImpl implements Some
      var count: number
      def Method(nr: number)
        echo nr
      enddef
    endclass

    interface Another
      var member: string
    endinterface

    class AnotherImpl implements Some, Another
      var member = 'abc'
      var count = 20
      def Method(nr: number)
        echo nr
      enddef
    endclass
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    interface Some
      var count: number
    endinterface

    class SomeImpl implements Some implements Some
      var count: number
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1350: Duplicate "implements"', 7)

  lines =<< trim END
    vim9script

    interface Some
      var count: number
    endinterface

    class SomeImpl implements Some, Some
      var count: number
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1351: Duplicate interface after "implements": Some', 7)

  lines =<< trim END
    vim9script

    interface Some
      var counter: number
      def Method(nr: number)
    endinterface

    class SomeImpl implements Some
      var count: number
      def Method(nr: number)
        echo nr
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1348: Variable "counter" of interface "Some" is not implemented', 13)

  lines =<< trim END
    vim9script

    interface Some
      var count: number
      def Methods(nr: number)
    endinterface

    class SomeImpl implements Some
      var count: number
      def Method(nr: number)
        echo nr
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1349: Method "Methods" of interface "Some" is not implemented', 13)

  # Check different order of members in class and interface works.
  lines =<< trim END
    vim9script

      interface Result
        var label: string
        var errpos: number
      endinterface

      # order of members is opposite of interface
      class Failure implements Result
        public var lnum: number = 5
        var errpos: number = 42
        var label: string = 'label'
      endclass

    def Test()
      var result: Result = Failure.new()

        assert_equal('label', result.label)
        assert_equal(42, result.errpos)
      enddef

    Test()
  END
  v9.CheckSourceSuccess(lines)

  # Interface name after "extends" doesn't end in a space or NUL character
  lines =<< trim END
    vim9script
    interface A
    endinterface
    class B extends A"
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1315: White space required after name: A"', 4)

  # Trailing characters after a class name
  lines =<< trim END
    vim9script
    class A bbb
    endclass
  END
  v9.CheckSourceFailure(lines, 'E488: Trailing characters: bbb', 2)

  # using "implements" with a non-existing class
  lines =<< trim END
    vim9script
    class A implements B
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1346: Interface name not found: B', 3)

  # using "implements" with a regular class
  lines =<< trim END
    vim9script
    class A
    endclass
    class B implements A
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1347: Not a valid interface: A', 5)

  # using "implements" with a variable
  lines =<< trim END
    vim9script
    var T: number = 10
    class A implements T
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1347: Not a valid interface: T', 4)

  # implements should be followed by a white space
  lines =<< trim END
    vim9script
    interface A
    endinterface
    class B implements A;
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1315: White space required after name: A;', 4)

  lines =<< trim END
    vim9script

    interface One
      def IsEven(nr: number): bool
    endinterface
    class Two implements One
      def IsEven(nr: number): string
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1383: Method "IsEven": type mismatch, expected func(number): bool but got func(number): string', 9)

  lines =<< trim END
    vim9script

    interface One
      def IsEven(nr: number): bool
    endinterface
    class Two implements One
      def IsEven(nr: bool): bool
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1383: Method "IsEven": type mismatch, expected func(number): bool but got func(bool): bool', 9)

  lines =<< trim END
    vim9script

    interface One
      def IsEven(nr: number): bool
    endinterface
    class Two implements One
      def IsEven(nr: number, ...extra: list<number>): bool
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1383: Method "IsEven": type mismatch, expected func(number): bool but got func(number, ...list<number>): bool', 9)

  # access superclass interface members from subclass, mix variable order
  lines =<< trim END
    vim9script

    interface I1
      var mvar1: number
      var mvar2: number
    endinterface

    # NOTE: the order is swapped
    class A implements I1
      var mvar2: number
      var mvar1: number
      public static var svar2: number
      public static var svar1: number
      def new()
        svar1 = 11
        svar2 = 12
        this.mvar1 = 111
        this.mvar2 = 112
      enddef
    endclass

    class B extends A
      def new()
        this.mvar1 = 121
        this.mvar2 = 122
      enddef
    endclass

    class C extends B
      def new()
        this.mvar1 = 131
        this.mvar2 = 132
      enddef
    endclass

    def F2(i: I1): list<number>
      return [ i.mvar1, i.mvar2 ]
    enddef

    var oa = A.new()
    var ob = B.new()
    var oc = C.new()

    assert_equal([111, 112], F2(oa))
    assert_equal([121, 122], F2(ob))
    assert_equal([131, 132], F2(oc))
  END
  v9.CheckSourceSuccess(lines)

  # Access superclass interface members from subclass, mix variable order.
  # Two interfaces, one on A, one on B; each has both kinds of variables
  lines =<< trim END
    vim9script

    interface I1
      var mvar1: number
      var mvar2: number
    endinterface

    interface I2
      var mvar3: number
      var mvar4: number
    endinterface

    class A implements I1
      public static var svar1: number
      public static var svar2: number
      var mvar1: number
      var mvar2: number
      def new()
        svar1 = 11
        svar2 = 12
        this.mvar1 = 111
        this.mvar2 = 112
      enddef
    endclass

    class B extends A implements I2
      static var svar3: number
      static var svar4: number
      var mvar3: number
      var mvar4: number
      def new()
        svar3 = 23
        svar4 = 24
        this.mvar1 = 121
        this.mvar2 = 122
        this.mvar3 = 123
        this.mvar4 = 124
      enddef
    endclass

    class C extends B
      public static var svar5: number
      def new()
        svar5 = 1001
        this.mvar1 = 131
        this.mvar2 = 132
        this.mvar3 = 133
        this.mvar4 = 134
      enddef
    endclass

    def F2(i: I1): list<number>
      return [ i.mvar1, i.mvar2 ]
    enddef

    def F4(i: I2): list<number>
      return [ i.mvar3, i.mvar4 ]
    enddef

    var oa = A.new()
    var ob = B.new()
    var oc = C.new()

    assert_equal([[111, 112]], [F2(oa)])
    assert_equal([[121, 122], [123, 124]], [F2(ob), F4(ob)])
    assert_equal([[131, 132], [133, 134]], [F2(oc), F4(oc)])
  END
  v9.CheckSourceSuccess(lines)

  # Using two interface names without a space after the ","
  lines =<< trim END
    vim9script
    interface A
    endinterface
    interface B
    endinterface
    class C implements A,B
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1315: White space required after name: A,B', 6)

  # No interface name after a comma
  lines =<< trim END
    vim9script
    interface A
    endinterface
    class B implements A,
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1389: Missing name after implements', 4)

  # No interface name after implements
  lines =<< trim END
    vim9script
    class A implements
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1389: Missing name after implements', 2)
enddef

def Test_call_interface_method()
  var lines =<< trim END
    vim9script
    interface Base
      def Enter(): void
    endinterface

    class Child implements Base
      def Enter(): void
        g:result ..= 'child'
      enddef
    endclass

    def F(obj: Base)
      obj.Enter()
    enddef

    g:result = ''
    F(Child.new())
    assert_equal('child', g:result)
    unlet g:result
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Base
      def Enter(): void
        g:result ..= 'base'
      enddef
    endclass

    class Child extends Base
      def Enter(): void
        g:result ..= 'child'
      enddef
    endclass

    def F(obj: Base)
      obj.Enter()
    enddef

    g:result = ''
    F(Child.new())
    assert_equal('child', g:result)
    unlet g:result
  END
  v9.CheckSourceSuccess(lines)

  # method of interface returns a value
  lines =<< trim END
    vim9script
    interface Base
      def Enter(): string
    endinterface

    class Child implements Base
      def Enter(): string
        g:result ..= 'child'
        return "/resource"
      enddef
    endclass

    def F(obj: Base)
      var r = obj.Enter()
      g:result ..= r
    enddef

    g:result = ''
    F(Child.new())
    assert_equal('child/resource', g:result)
    unlet g:result
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Base
      def Enter(): string
        return null_string
      enddef
    endclass

    class Child extends Base
      def Enter(): string
        g:result ..= 'child'
        return "/resource"
      enddef
    endclass

    def F(obj: Base)
      var r = obj.Enter()
      g:result ..= r
    enddef

    g:result = ''
    F(Child.new())
    assert_equal('child/resource', g:result)
    unlet g:result
  END
  v9.CheckSourceSuccess(lines)

  # No class that implements the interface.
  lines =<< trim END
    vim9script

    interface IWithEE
      def Enter(): any
      def Exit(): void
    endinterface

    def With1(ee: IWithEE, F: func)
      var r = ee.Enter()
    enddef

    defcompile
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_class_used_as_type()
  var lines =<< trim END
    vim9script

    class Point
      var x = 0
      var y = 0
    endclass

    var p: Point
    p = Point.new(2, 33)
    assert_equal(2, p.x)
    assert_equal(33, p.y)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    interface HasX
      var x: number
    endinterface

    class Point implements HasX
      var x = 0
      var y = 0
    endclass

    var p: Point
    p = Point.new(2, 33)
    var hx = p
    assert_equal(2, hx.x)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class Point
      var x = 0
      var y = 0
    endclass

    var p: Point
    p = 'text'
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected object<Point> but got string', 9)
enddef

def Test_class_extends()
  var lines =<< trim END
    vim9script
    class Base
      var one = 1
      def GetOne(): number
        return this.one
      enddef
    endclass
    class Child extends Base
      var two = 2
      def GetTotal(): number
        return this.one + this.two
      enddef
    endclass
    var o = Child.new()
    assert_equal(1, o.one)
    assert_equal(2, o.two)
    assert_equal(1, o.GetOne())
    assert_equal(3, o.GetTotal())
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Base
      var one = 1
    endclass
    class Child extends Base
      var two = 2
    endclass
    var o = Child.new(3, 44)
    assert_equal(3, o.one)
    assert_equal(44, o.two)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Base
      var one = 1
    endclass
    class Child extends Base extends Base
      var two = 2
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1352: Duplicate "extends"', 5)

  lines =<< trim END
    vim9script
    class Child extends BaseClass
      var two = 2
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1353: Class name not found: BaseClass', 4)

  lines =<< trim END
    vim9script
    var SomeVar = 99
    class Child extends SomeVar
      var two = 2
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1354: Cannot extend SomeVar', 5)

  lines =<< trim END
    vim9script
    class Child
      var age: number
      def ToString(): number
        return this.age
      enddef
      def ToString(): string
        return this.age
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: ToString', 9)

  lines =<< trim END
    vim9script
    class Base
      var name: string
      static def ToString(): string
        return 'Base class'
      enddef
    endclass

    class Child extends Base
      var age: number
      def ToString(): string
        return Base.ToString() .. ': ' .. this.age
      enddef
    endclass

    var o = Child.new('John', 42)
    assert_equal('Base class: 42', o.ToString())
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Base
      var value = 1
      def new(init: number)
        this.value = number + 1
      enddef
    endclass
    class Child extends Base
      def new()
        this.new(3)
      enddef
    endclass
    var c = Child.new()
  END
  v9.CheckSourceFailure(lines, 'E1385: Class method "new" accessible only using class "Child"', 1)

  # base class with more than one object member
  lines =<< trim END
    vim9script

    class Result
      var success: bool
      var value: any = null
    endclass

    class Success extends Result
      def new(this.value = v:none)
        this.success = true
      enddef
    endclass

    var v = Success.new('asdf')
    assert_equal("object of Success {success: true, value: 'asdf'}", string(v))
  END
  v9.CheckSourceSuccess(lines)

  # class name after "extends" doesn't end in a space or NUL character
  lines =<< trim END
    vim9script
    class A
    endclass
    class B extends A"
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1315: White space required after name: A"', 4)
enddef

def Test_using_base_class()
  var lines =<< trim END
    vim9script

    class BaseEE
      def Enter(): any
        return null
      enddef
      def Exit(resource: any): void
      enddef
    endclass

    class ChildEE extends BaseEE
      def Enter(): any
        return 42
      enddef

      def Exit(resource: number): void
        g:result ..= '/exit'
      enddef
    endclass

    def With(ee: BaseEE)
      var r = ee.Enter()
      try
        g:result ..= r
      finally
        g:result ..= '/finally'
        ee.Exit(r)
      endtry
    enddef

    g:result = ''
    With(ChildEE.new())
    assert_equal('42/finally/exit', g:result)
  END
  v9.CheckSourceSuccess(lines)
  unlet g:result
enddef

" Test for using a method from the super class
def Test_super_dispatch()
  # See #15448 and #15463
  var lines =<< trim END
    vim9script

    class A
      def String(): string
        return 'A'
      enddef
    endclass

    class B extends A
      def String(): string
        return super.String()
      enddef
    endclass

    class C extends B
    endclass

    assert_equal('A', C.new().String())
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class A
      def F(): string
        return 'AA'
      enddef
    endclass

    class B extends A
      def F(): string
        return 'BB'
      enddef
      def S(): string
        return super.F()
      enddef
      def S0(): string
        return this.S()
      enddef
    endclass

    class C extends B
      def F(): string
        return 'CC'
      enddef
      def ToB(): string
        return super.F()
      enddef
    endclass

    assert_equal('AA', B.new().S())
    assert_equal('AA', C.new().S())
    assert_equal('AA', B.new().S0())
    assert_equal('AA', C.new().S0())

    assert_equal('BB', C.new().ToB())

    assert_equal('CC', C.new().F())
    assert_equal('BB', B.new().F())
    assert_equal('AA', A.new().F())
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    var call_chain: list<string>

    abstract class A
      abstract def _G(): string

      def F(): string
        call_chain->add('A.F()')
        return this._G()
      enddef
      def _H(): string
        call_chain->add('A._H()')
        return this.F()
      enddef
    endclass

    class B extends A
      def _G(): string
        call_chain->add('B.G()')
        return 'BBB'
      enddef
      def SF(): string
        call_chain->add('B.SF()')
        return super._H()
      enddef
    endclass

    class C extends B
    endclass

    class D extends C
      def SF(): string
        call_chain->add('D.SF()')
        return super.SF()
      enddef
    endclass

    class E extends D
      def SF(): string
        call_chain->add('E.SF()')
        return super.SF()
      enddef
    endclass

    class F extends E
      def _G(): string
        call_chain->add('F._G()')
        return 'FFF'
      enddef
    endclass

    # E.new() -> A.F() -> B._G()
    call_chain = []
    var o1 = E.new()
    assert_equal('BBB', o1.F())
    assert_equal(['A.F()', 'B.G()'], call_chain)

    # F.new() -> E.SF() -> D.SF() -> B.SF() -> A._H() -> A.F() -> F._G()
    call_chain = []
    var o2 = F.new()
    assert_equal('FFF', o2.SF())
    assert_equal(['E.SF()', 'D.SF()', 'B.SF()', 'A._H()', 'A.F()', 'F._G()'], call_chain)
  END
  v9.CheckSourceSuccess(lines)

  # problems with method dispatch: super -> abstract
  # https://github.com/vim/vim/issues/15514
  lines =<< trim END
    vim9script
    abstract class B
      abstract def ToString(): string
    endclass

    class C extends B
      def ToString(): string
        return super.ToString()
      enddef
    endclass

    try
      defcompile C.ToString
      call assert_false(1, 'command should have failed')
    catch
      call assert_exception('E1431: Abstract method "ToString" in class "B" cannot be accessed directly')
    endtry
  END
  v9.CheckSourceSuccess(lines)

  # problems with method dispatch: super -> abstract -> concrete
  lines =<< trim END
    vim9script

    class A
      def ToString()
        echo 'A'
      enddef
    endclass

    abstract class B extends A
      abstract def ToString()
    endclass

    class C extends B
      def ToString()
        super.ToString()
      enddef
    endclass

    try
      defcompile C.ToString
      call assert_false(1, 'command should have failed')
    catch
      call assert_exception('E1431: Abstract method "ToString" in class "B" cannot be accessed directly')
    endtry
  END
  v9.CheckSourceSuccess(lines)

  # Invoking a super method and an interface method which have the same name.
  lines =<< trim END
    vim9script

    interface I
      def ToString(): string
    endinterface

    # Note that A does not implement I.
    class A
      def ToString(): string
        return 'A'
      enddef
    endclass

    class B extends A implements I
      def ToString(): string
        return super.ToString()
      enddef
    endclass

     def TestI(i: I): string
       return i.ToString()
     enddef

     assert_equal('A', B.new().ToString())
     assert_equal('A', TestI(B.new()))
  END
  v9.CheckSourceSuccess(lines)

  # super and an abstract class with no abstract methods
  lines =<< trim END
    vim9script

    class A
      def ToString(): string
        return 'A'
      enddef
    endclass

    # An abstract class with no abstract methods.
    abstract class B extends A
    endclass

    class C extends B
      def ToString(): string
        return super.ToString()
      enddef
    endclass

    def TestA(a: A): string
      return a.ToString()
    enddef

    def TestB(b: B): string
      return b.ToString()
    enddef

    assert_equal('A', C.new().ToString())
    assert_equal('A', TestA(A.new()))
    assert_equal('A', TestA(C.new()))
    assert_equal('A', TestB(C.new()))
  END
  v9.CheckSourceSuccess(lines)

  # super and an abstract class with no abstract methods and the initial
  # implements clause
  lines =<< trim END
    vim9script

    interface I
      def ToString(): string
    endinterface

    # Note that A does not implement I.
    class A
      def ToString(): string
        return 'A'
      enddef
    endclass

    # An abstract class with no abstract methods.
    abstract class B extends A implements I
    endclass

    class C extends B implements I
      def ToString(): string
        return super.ToString()
      enddef
    endclass

    # Note that A.ToString() is different from I.ToString().
    def TestA(a: A): string
      return a.ToString()
    enddef

    assert_equal('A', C.new().ToString())
    assert_equal('A', TestA(A.new()))
    assert_equal('A', TestA(C.new()))
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_class_import()
  var lines =<< trim END
    vim9script
    export class Animal
      var kind: string
      var name: string
    endclass
  END
  writefile(lines, 'Xanimal.vim', 'D')

  lines =<< trim END
    vim9script
    import './Xanimal.vim' as animal

    var a: animal.Animal
    a = animal.Animal.new('fish', 'Eric')
    assert_equal('fish', a.kind)
    assert_equal('Eric', a.name)

    var b: animal.Animal = animal.Animal.new('cat', 'Garfield')
    assert_equal('cat', b.kind)
    assert_equal('Garfield', b.name)
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for importing a class into a legacy script and calling the class method
def Test_class_method_from_legacy_script()
  var lines =<< trim END
    vim9script
    export class A
      static var name: string = 'a'
      static def SetName(n: string)
        name = n
      enddef
    endclass
  END
  writefile(lines, 'Xvim9export.vim', 'D')

  lines =<< trim END
    import './Xvim9export.vim' as vim9

    call s:vim9.A.SetName('b')
    call assert_equal('b', s:vim9.A.name)
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for implementing an imported interface
def Test_implement_imported_interface()
  var lines =<< trim END
    vim9script
    export interface Imp_Intf1
      def Fn1(): number
    endinterface
    export interface Imp_Intf2
      def Fn2(): number
    endinterface
  END
  writefile(lines, 'Ximportinterface.vim', 'D')

  lines =<< trim END
    vim9script
    import './Ximportinterface.vim' as Xintf

    class A implements Xintf.Imp_Intf1, Xintf.Imp_Intf2
      def Fn1(): number
        return 10
      enddef
      def Fn2(): number
        return 20
      enddef
    endclass
    var a = A.new()
    assert_equal(10, a.Fn1())
    assert_equal(20, a.Fn2())
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for extending an imported class
def Test_extend_imported_class()
  var lines =<< trim END
    vim9script
    export class Imp_C1
      def Fn1(): number
        return 5
      enddef
    endclass
  END
  writefile(lines, 'Xextendimportclass.vim', 'D')

  lines =<< trim END
    vim9script
    import './Xextendimportclass.vim' as XClass

    class A extends XClass.Imp_C1
    endclass
    var a = A.new()
    assert_equal(5, a.Fn1())
  END
  v9.CheckScriptSuccess(lines)
enddef

def Test_abstract_class()
  var lines =<< trim END
    vim9script
    abstract class Base
      var name: string
    endclass
    class Person extends Base
      var age: number
    endclass
    var p: Base = Person.new('Peter', 42)
    assert_equal('Peter', p.name)
    assert_equal(42, p.age)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    abstract class Base
      var name: string
    endclass
    class Person extends Base
      var age: number
    endclass
    var p = Base.new('Peter')
  END
  v9.CheckSourceFailure(lines, 'E1325: Method "new" not found in class "Base"', 8)

  lines =<< trim END
    abstract class Base
      var name: string
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1316: Class can only be defined in Vim9 script', 1)

  # Test for "abstract" cannot be abbreviated
  lines =<< trim END
    vim9script
    abs class A
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: abs class A', 2)

  # Additional commands after "abstract class"
  lines =<< trim END
    vim9script
    abstract class Something | var x = []
    endclass
  END
  v9.CheckSourceFailure(lines, "E488: Trailing characters: | var x = []", 2)

  # Abstract class cannot have a "new" function
  lines =<< trim END
    vim9script
    abstract class Base
      def new()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1359: Cannot define a "new" method in an abstract class', 4)

  # extending an abstract class with class methods and variables
  lines =<< trim END
    vim9script
    abstract class A
      static var s: string = 'vim'
      static def Fn(): list<number>
        return [10]
      enddef
    endclass
    class B extends A
    endclass
    var b = B.new()
    assert_equal('vim', A.s)
    assert_equal([10], A.Fn())
  END
  v9.CheckScriptSuccess(lines)
enddef

def Test_closure_in_class()
  var lines =<< trim END
    vim9script

    class Foo
      var y: list<string> = ['B']

      def new()
        g:result = filter(['A', 'B'], (_, v) => index(this.y, v) == -1)
      enddef
    endclass

    Foo.new()
    assert_equal(['A'], g:result)
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_construct_object_from_legacy()
  # Cannot directly invoke constructor from legacy
  var lines =<< trim END
    vim9script

    var newCalled = false

    class A
      def new(arg: string)
        newCalled = true
      enddef
    endclass

    export def CreateA(...args: list<any>): A
      return call(A.new, args)
    enddef

    g:P = CreateA
    legacy call g:P('some_arg')
    assert_equal(true, newCalled)
    unlet g:P
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    var newCalled = false

    class A
      static def CreateA(options = {}): any
        return A.new()
      enddef
      def new()
        newCalled = true
      enddef
    endclass

    g:P = A.CreateA
    legacy call g:P()
    assert_equal(true, newCalled)
    unlet g:P
  END
  v9.CheckSourceSuccess(lines)

  # This also tests invoking "new()" with "call"
  lines =<< trim END
    vim9script

    var createdObject: any

    class A
      var val1: number
      var val2: number
      static def CreateA(...args: list<any>): any
        createdObject = call(A.new, args)
        return createdObject
      enddef
    endclass

    g:P = A.CreateA
    legacy call g:P(3, 5)
    assert_equal(3, createdObject.val1)
    assert_equal(5, createdObject.val2)
    legacy call g:P()
    assert_equal(0, createdObject.val1)
    assert_equal(0, createdObject.val2)
    legacy call g:P(7)
    assert_equal(7, createdObject.val1)
    assert_equal(0, createdObject.val2)
    unlet g:P
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_defer_with_object()
  var lines =<< trim END
    vim9script

    class CWithEE
      def Enter()
        g:result ..= "entered/"
      enddef
      def Exit()
        g:result ..= "exited"
      enddef
    endclass

    def With(ee: CWithEE, F: func)
      ee.Enter()
      defer ee.Exit()
      F()
    enddef

    g:result = ''
    var obj = CWithEE.new()
    obj->With(() => {
      g:result ..= "called/"
    })
    assert_equal('entered/called/exited', g:result)
  END
  v9.CheckSourceSuccess(lines)
  unlet g:result

  lines =<< trim END
    vim9script

    class BaseWithEE
      def Enter()
        g:result ..= "entered-base/"
      enddef
      def Exit()
        g:result ..= "exited-base"
      enddef
    endclass

    class CWithEE extends BaseWithEE
      def Enter()
        g:result ..= "entered-child/"
      enddef
      def Exit()
        g:result ..= "exited-child"
      enddef
    endclass

    def With(ee: BaseWithEE, F: func)
      ee.Enter()
      defer ee.Exit()
      F()
    enddef

    g:result = ''
    var obj = CWithEE.new()
    obj->With(() => {
      g:result ..= "called/"
    })
    assert_equal('entered-child/called/exited-child', g:result)
  END
  v9.CheckSourceSuccess(lines)
  unlet g:result
enddef

" The following test used to crash Vim (Github issue #12676)
def Test_extends_method_crashes_vim()
  var lines =<< trim END
    vim9script

    class Observer
    endclass

    class Property
      var value: any

      def Set(v: any)
        if v != this.value
          this.value = v
        endif
      enddef

      def Register(observer: Observer)
      enddef
    endclass

    class Bool extends Property
      var value2: bool
    endclass

    def Observe(obj: Property, who: Observer)
      obj.Register(who)
    enddef

    var p = Bool.new(false)
    var myObserver = Observer.new()

    Observe(p, myObserver)

    p.Set(true)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for calling a method in a class that is extended
def Test_call_method_in_extended_class()
  var lines =<< trim END
    vim9script

    var prop_init_called = false
    var prop_register_called = false

    class Property
      def Init()
        prop_init_called = true
      enddef

      def Register()
        prop_register_called = true
      enddef
    endclass

    class Bool extends Property
    endclass

    def Observe(obj: Property)
      obj.Register()
    enddef

    var p = Property.new()
    Observe(p)

    p.Init()
    assert_true(prop_init_called)
    assert_true(prop_register_called)
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_instanceof()
  var lines =<< trim END
    vim9script

    class Base1
    endclass

    class Base2 extends Base1
    endclass

    interface Intf1
    endinterface

    class Mix1 implements Intf1
    endclass

    class Base3 extends Mix1
    endclass

    type AliasBase1 = Base1
    type AliasBase2 = Base2
    type AliasIntf1 = Intf1
    type AliasMix1 = Mix1

    var b1 = Base1.new()
    var b2 = Base2.new()
    var b3 = Base3.new()

    assert_true(instanceof(b1, Base1))
    assert_true(instanceof(b2, Base1))
    assert_false(instanceof(b1, Base2))
    assert_true(instanceof(b3, Mix1))
    assert_true(instanceof(b3, Base1, Base2, Intf1))

    assert_true(instanceof(b1, AliasBase1))
    assert_true(instanceof(b2, AliasBase1))
    assert_false(instanceof(b1, AliasBase2))
    assert_true(instanceof(b3, AliasMix1))
    assert_true(instanceof(b3, AliasBase1, AliasBase2, AliasIntf1))

    def Foo()
      var a1 = Base1.new()
      var a2 = Base2.new()
      var a3 = Base3.new()

      assert_true(instanceof(a1, Base1))
      assert_true(instanceof(a2, Base1))
      assert_false(instanceof(a1, Base2))
      assert_true(instanceof(a3, Mix1))
      assert_true(instanceof(a3, Base1, Base2, Intf1))

      assert_true(instanceof(a1, AliasBase1))
      assert_true(instanceof(a2, AliasBase1))
      assert_false(instanceof(a1, AliasBase2))
      assert_true(instanceof(a3, AliasMix1))
      assert_true(instanceof(a3, AliasBase1, AliasBase2, AliasIntf1))
    enddef
    Foo()

    var o_null: Base1
    assert_false(instanceof(o_null, Base1))

  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class Base1
    endclass
    instanceof(Base1.new())
  END
  v9.CheckSourceFailure(lines, 'E119: Not enough arguments for function: instanceof')

  lines =<< trim END
    vim9script

    class Base1
    endclass
    def F()
      instanceof(Base1.new())
    enddef
    F()
  END
  v9.CheckSourceFailure(lines, 'E119: Not enough arguments for function: instanceof')

  lines =<< trim END
    vim9script

    class Base1
    endclass

    class Base2
    endclass

    var o = Base2.new()
    instanceof(o, Base1, Base2, 3)
  END
  v9.CheckSourceFailure(lines, 'E693: Class or class typealias required for argument 4', 10)

  lines =<< trim END
    vim9script

    class Base1
    endclass

    class Base2
    endclass

    def F()
      var o = Base2.new()
      instanceof(o, Base1, Base2, 3)
    enddef
    F()
  END
  v9.CheckSourceFailure(lines, 'E693: Class or class typealias required for argument 4')
enddef

" Test for calling a method in the parent class that is extended partially.
" This used to fail with the 'E118: Too many arguments for function: Text' error
" message (Github issue #12524).
def Test_call_method_in_parent_class()
  var lines =<< trim END
    vim9script

    class Widget
      var _lnum: number = 1

      def SetY(lnum: number)
        this._lnum = lnum
      enddef

      def Text(): string
        return ''
      enddef
    endclass

    class Foo extends Widget
      def Text(): string
        return '<Foo>'
      enddef
    endclass

    def Stack(w1: Widget, w2: Widget): list<Widget>
      w1.SetY(1)
      w2.SetY(2)
      return [w1, w2]
    enddef

    var foo1 = Foo.new()
    var foo2 = Foo.new()
    var l = Stack(foo1, foo2)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for calling methods from three levels of classes
def Test_multi_level_method_call()
  var lines =<< trim END
    vim9script

    var A_func1: number = 0
    var A_func2: number = 0
    var A_func3: number = 0
    var B_func2: number = 0
    var B_func3: number = 0
    var C_func3: number = 0

    class A
      def Func1()
        A_func1 += 1
      enddef

      def Func2()
        A_func2 += 1
      enddef

      def Func3()
        A_func3 += 1
      enddef
    endclass

    class B extends A
      def Func2()
        B_func2 += 1
      enddef

      def Func3()
        B_func3 += 1
      enddef
    endclass

    class C extends B
      def Func3()
        C_func3 += 1
      enddef
    endclass

    def A_CallFuncs(a: A)
      a.Func1()
      a.Func2()
      a.Func3()
    enddef

    def B_CallFuncs(b: B)
      b.Func1()
      b.Func2()
      b.Func3()
    enddef

    def C_CallFuncs(c: C)
      c.Func1()
      c.Func2()
      c.Func3()
    enddef

    var cobj = C.new()
    A_CallFuncs(cobj)
    B_CallFuncs(cobj)
    C_CallFuncs(cobj)
    assert_equal(3, A_func1)
    assert_equal(0, A_func2)
    assert_equal(0, A_func3)
    assert_equal(3, B_func2)
    assert_equal(0, B_func3)
    assert_equal(3, C_func3)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using members from three levels of classes
def Test_multi_level_member_access()
  var lines =<< trim END
    vim9script

    class A
      public var val1: number = 0
    endclass

    class B extends A
      public var val2: number = 0
    endclass

    class C extends B
      public var val3: number = 0
    endclass

    def A_members(a: A)
      a.val1 += 1
    enddef

    def B_members(b: B)
      b.val1 += 1
      b.val2 += 1
    enddef

    def C_members(c: C)
      c.val1 += 1
      c.val2 += 1
      c.val3 += 1
    enddef

    var cobj = C.new()
    A_members(cobj)
    B_members(cobj)
    C_members(cobj)
    assert_equal(3, cobj.val1)
    assert_equal(2, cobj.val2)
    assert_equal(1, cobj.val3)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test expansion of <stack> with class methods.
def Test_stack_expansion_with_methods()
  var lines =<< trim END
    vim9script

    class C
      def M1()
        F0()
      enddef
    endclass

    def F0()
      assert_match('<SNR>\d\+_F\[1\]\.\.<SNR>\d\+_C\.M1\[1\]\.\.<SNR>\d\+_F0\[1\]$', expand('<stack>'))
    enddef

    def F()
      C.new().M1()
    enddef

    F()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test the return type of the new() constructor
def Test_new_return_type()
  # new() uses the default return type and there is no return statement
  var lines =<< trim END
    vim9script

    class C
      var _bufnr: number

      def new(this._bufnr)
        if !bufexists(this._bufnr)
          this._bufnr = -1
        endif
      enddef
    endclass

    var c = C.new(12345)
    assert_equal('object<C>', typename(c))

    var v1: C
    v1 = C.new(12345)
    assert_equal('object<C>', typename(v1))

    def F()
      var v2: C
      v2 = C.new(12345)
      assert_equal('object<C>', typename(v2))
    enddef
    F()
  END
  v9.CheckSourceSuccess(lines)

  # new() uses the default return type and an empty 'return' statement
  lines =<< trim END
    vim9script

    class C
      var _bufnr: number

      def new(this._bufnr)
        if !bufexists(this._bufnr)
          this._bufnr = -1
          return
        endif
      enddef
    endclass

    var c = C.new(12345)
    assert_equal('object<C>', typename(c))

    var v1: C
    v1 = C.new(12345)
    assert_equal('object<C>', typename(v1))

    def F()
      var v2: C
      v2 = C.new(12345)
      assert_equal('object<C>', typename(v2))
    enddef
    F()
  END
  v9.CheckSourceSuccess(lines)

  # new() uses "any" return type and returns "this"
  lines =<< trim END
    vim9script

    class C
      var _bufnr: number

      def new(this._bufnr): any
        if !bufexists(this._bufnr)
          this._bufnr = -1
          return this
        endif
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1365: Cannot use a return type with the "new" method', 11)

  # new() uses 'Dict' return type and returns a Dict
  lines =<< trim END
    vim9script

    class C
      var _state: dict<any>

      def new(): dict<any>
        this._state = {}
        return this._state
      enddef
    endclass

    var c = C.new()
    assert_equal('object<C>', typename(c))
  END
  v9.CheckSourceFailure(lines, 'E1365: Cannot use a return type with the "new" method', 9)
enddef

" Test for checking a member initialization type at run time.
def Test_runtime_type_check_for_member_init()
  var lines =<< trim END
    vim9script

    var retnum: bool = false

    def F(): any
      retnum = !retnum
      if retnum
        return 1
      else
        return "hello"
      endif
    enddef

    class C
      var _foo: bool = F()
    endclass

    var c1 = C.new()
    var c2 = C.new()
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected bool but got string', 0)
enddef

" Test for locking a variable referring to an object and reassigning to another
" object.
def Test_lockvar_object()
  var lines =<< trim END
    vim9script

    class C
      var val: number
      def new(this.val)
      enddef
    endclass

    var some_dict: dict<C> = { a: C.new(1), b: C.new(2), c: C.new(3), }
    lockvar 2 some_dict

    var current: C
    current = some_dict['c']
    assert_equal(3, current.val)
    current = some_dict['b']
    assert_equal(2, current.val)

    def F()
      current = some_dict['c']
    enddef

    def G()
      current = some_dict['b']
    enddef

    F()
    assert_equal(3, current.val)
    G()
    assert_equal(2, current.val)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test trying to lock an object variable from various places
def Test_lockvar_object_variable()
  # An object variable lockvar has several cases:
  # object method, scriptlevel, scriplevel from :def, :def arg
  # method arg, static method arg.
  # Also different depths

  #
  # lockvar of read-only object variable
  #

  # read-only lockvar from object method
  var lines =<< trim END
    vim9script

    class C
      var val1: number
      def Lock()
        lockvar this.val1
      enddef
    endclass
    var o = C.new(3)
    o.Lock()
  END
  v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "this.val1" in class "C"')

  # read-only lockvar from scriptlevel
  lines =<< trim END
    vim9script

    class C
      var val2: number
    endclass
    var o = C.new(3)
    lockvar o.val2
  END
  v9.CheckSourceFailure(lines, 'E1335: Variable "val2" in class "C" is not writable')

  # read-only lockvar of scriptlevel variable from def
  lines =<< trim END
    vim9script

    class C
      var val3: number
    endclass
    var o = C.new(3)
    def Lock()
      lockvar o.val3
    enddef
    Lock()
  END
  v9.CheckSourceFailure(lines, 'E1335: Variable "val3" in class "C" is not writable')

  # read-only lockvar of def argument variable
  lines =<< trim END
    vim9script

    class C
      var val4: number
    endclass
    def Lock(o: C)
      lockvar o.val4
    enddef
    Lock(C.new(3))
  END
  v9.CheckSourceFailure(lines, 'E1335: Variable "val4" in class "C" is not writable')

  # read-only lockvar from object method arg
  lines =<< trim END
    vim9script

    class C
      var val5: number
      def Lock(c: C)
        lockvar c.val5
      enddef
    endclass
    var o = C.new(3)
    o.Lock(C.new(5))
  END
  v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "c.val5" in class "C"')

  # read-only lockvar from class method arg
  lines =<< trim END
    vim9script

    class C
      var val6: number
      static def Lock(c: C)
        lockvar c.val6
      enddef
    endclass
    var o = C.new(3)
    C.Lock(o)
  END
  v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "c.val6" in class "C"')

  #
  # lockvar of public object variable
  #

  # lockvar from object method
  lines =<< trim END
    vim9script

    class C
      public var val1: number
      def Lock()
        lockvar this.val1
      enddef
    endclass
    var o = C.new(3)
    o.Lock()
  END
  v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "this.val1" in class "C"', 1)

  # lockvar from scriptlevel
  lines =<< trim END
    vim9script

    class C
      public var val2: number
    endclass
    var o = C.new(3)
    lockvar o.val2
  END
  v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o.val2" in class "C"', 7)

  # lockvar of scriptlevel variable from def
  lines =<< trim END
    vim9script

    class C
      public var val3: number
    endclass
    var o = C.new(3)
    def Lock()
      lockvar o.val3
    enddef
    Lock()
  END
  v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o.val3" in class "C"', 1)

  # lockvar of def argument variable
  lines =<< trim END
    vim9script

    class C
      public var val4: number
    endclass
    def Lock(o: C)
      lockvar o.val4
    enddef
    Lock(C.new(3))
  END
  v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o.val4" in class "C"', 1)

  # lockvar from object method arg
  lines =<< trim END
    vim9script

    class C
      public var val5: number
      def Lock(c: C)
        lockvar c.val5
      enddef
    endclass
    var o = C.new(3)
    o.Lock(C.new(5))
  END
  v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "c.val5" in class "C"', 1)

  # lockvar from class method arg
  lines =<< trim END
    vim9script

    class C
      public var val6: number
      static def Lock(c: C)
        lockvar c.val6
      enddef
    endclass
    var o = C.new(3)
    C.Lock(o)
  END
  v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "c.val6" in class "C"', 1)
enddef

" Test trying to lock a class variable from various places
def Test_lockvar_class_variable()

  # lockvar bare static from object method
  var lines =<< trim END
    vim9script

    class C
      public static var sval1: number
      def Lock()
        lockvar sval1
      enddef
    endclass
    var o = C.new()
    o.Lock()
  END
  v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "sval1" in class "C"', 1)

  # lockvar C.static from object method
  lines =<< trim END
    vim9script

    class C
      public static var sval2: number
      def Lock()
        lockvar C.sval2
      enddef
    endclass
    var o = C.new()
    o.Lock()
  END
  v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "C.sval2" in class "C"', 1)

  # lockvar bare static from class method
  lines =<< trim END
    vim9script

    class C
      public static var sval3: number
      static def Lock()
        lockvar sval3
      enddef
    endclass
    C.Lock()
  END
  v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "sval3" in class "C"', 1)

  # lockvar C.static from class method
  lines =<< trim END
    vim9script

    class C
      public static var sval4: number
      static def Lock()
        lockvar C.sval4
      enddef
    endclass
    C.Lock()
  END
  v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "C.sval4" in class "C"', 1)

  # lockvar C.static from script level
  lines =<< trim END
    vim9script

    class C
      public static var sval5: number
    endclass
    lockvar C.sval5
  END
  v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "C.sval5" in class "C"', 6)

  # lockvar o.static from script level
  lines =<< trim END
    vim9script

    class C
      public static var sval6: number
    endclass
    var o = C.new()
    lockvar o.sval6
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "sval6" accessible only using class "C"', 7)
enddef

" Test locking an argument to :def
def Test_lockvar_argument()
  # Lockvar a function arg
  var lines =<< trim END
    vim9script

    def Lock(val: any)
        lockvar val
    enddef

    var d = {a: 1, b: 2}
    Lock(d)

    d->extend({c: 3})
  END
  v9.CheckSourceFailure(lines, 'E741: Value is locked: extend() argument')

  # Lockvar a function arg. Verify "sval" is interpreted as argument and not a
  # class member in "C". This tests lval_root_is_arg.
  lines =<< trim END
    vim9script

    class C
      public static var sval: list<number>
    endclass

    def Lock2(sval: any)
      lockvar sval
    enddef

    var o = C.new()
    Lock2(o)
  END
  v9.CheckSourceSuccess(lines)

  # Lock a class.
  lines =<< trim END
    vim9script

    class C
      public static var sval: list<number>
    endclass

    def Lock2(sval: any)
      lockvar sval
    enddef

    Lock2(C)
  END
  v9.CheckSourceFailure(lines, 'E1405: Class "C" cannot be used as a value')

  # Lock an object.
  lines =<< trim END
    vim9script

    class C
      public static var sval: list<number>
    endclass

    def Lock2(sval: any)
      lockvar sval
    enddef

    Lock2(C.new())
  END
  v9.CheckSourceSuccess(lines)

  # In this case (unlike previous) "lockvar sval" is a class member.
  lines =<< trim END
    vim9script

    class C
      public static var sval: list<number>
      def Lock2()
        lockvar sval
      enddef
    endclass


    var o = C.new()
    o.Lock2()
  END
  v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "sval" in class "C"', 1)
enddef

" Test that this can be locked without error
def Test_lockvar_this()
  # lockvar this
  var lines =<< trim END
    vim9script
    class C
      def TLock()
        lockvar this
      enddef
    endclass
    var o = C.new()
    o.TLock()
  END
  v9.CheckSourceSuccess(lines)

  # lockvar four   (four letter word, but not this)
  lines =<< trim END
    vim9script
    class C
      def TLock4()
        var four: number
        lockvar four
      enddef
    endclass
    var o = C.new()
    o.TLock4()
  END
  v9.CheckSourceFailure(lines, 'E1178: Cannot lock or unlock a local variable')

  # lockvar this5; "this" + one char, 5 letter word, starting with "this"
  lines =<< trim END
    vim9script
    class C
      def TLock5()
        var this5: number
        lockvar this5
      enddef
    endclass
    var o = C.new()
    o.TLock5()
  END
  v9.CheckSourceFailure(lines, 'E1178: Cannot lock or unlock a local variable')
enddef

" Test some general lockvar cases
def Test_lockvar_general()
  # lockvar an object and a class. It does nothing
  var lines =<< trim END
    vim9script
    class C
    endclass
    var o = C.new()
    lockvar o
    lockvar C
  END
  v9.CheckSourceSuccess(lines)

  # Lock a list element that's nested in an object variable from a :def
  lines =<< trim END
    vim9script

    class C
      public var val: list<list<number>> = [ [1], [2], [3] ]
    endclass
    def Lock2(obj: any)
      lockvar obj.val[1]
    enddef

    var o = C.new()
    Lock2(o)
    o.val[0] = [9]
    assert_equal([ [9], [2], [3] ], o.val)
    try
      o.val[1] = [999]
      call assert_false(true, 'assign should have failed')
    catch
      assert_exception('E741:')
    endtry
    o.val[2] = [8]
    assert_equal([ [9], [2], [8] ], o.val)
  END
  v9.CheckSourceSuccess(lines)

  # Lock a list element that's nested in an object variable from scriptlevel
  lines =<< trim END
    vim9script

    class C
      public var val: list<list<number>> = [ [1], [2], [3] ]
    endclass

    var o = C.new()
    lockvar o.val[1]
    o.val[0] = [9]
    assert_equal([ [9], [2], [3] ], o.val)
    try
      o.val[1] = [999]
      call assert_false(true, 'assign should have failed')
    catch
      assert_exception('E741:')
    endtry
    o.val[2] = [8]
    assert_equal([ [9], [2], [8] ], o.val)
  END
  v9.CheckSourceSuccess(lines)

  # lock a script level variable from an object method
  lines =<< trim END
    vim9script

    class C
      def Lock()
        lockvar l
      enddef
    endclass

    var l = [1]
    C.new().Lock()
    l[0] = 11
  END
  v9.CheckSourceFailure(lines, 'E741: Value is locked: l[0] = 11', 11)

  # lock a list element referenced by a protected object variable
  # in an object fetched via a script level list
  lines =<< trim END
    vim9script

    class C
      var _v1: list<list<number>>
      def Lock()
        lockvar lc[0]._v1[1]
      enddef
    endclass

    var l = [[1], [2], [3]]
    var o = C.new(l)
    var lc: list<C> = [ o ]

    o.Lock()
    l[0] = [22]
    l[1] = [33]
  END
  v9.CheckSourceFailure(lines, 'E741: Value is locked: l[1] = [33]', 16)

  # similar to the previous test, except the locking code is executing
  # in a class that does not own the protected variable.
  # Note that the locking code is in a class has a protected variable of
  # the same name.
  lines =<< trim END
    vim9script

    class C2
      var _v1: list<list<number>>
      def Lock(obj: any)
        lockvar lc[0]._v1[1]
      enddef
    endclass

    class C
      var _v1: list<list<number>>
    endclass

    var l = [[1], [2], [3]]
    var o = C.new(l)
    var lc: list<C> = [ o ]

    var o2 = C2.new()
    o2.Lock(o)
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_v1" in class "C"')
enddef

" Test builtin islocked()
def Test_lockvar_islocked()
  # Can't lock class/object variable
  # Lock class/object variable's value
  # Lock item of variable's value (a list item)
  # variable is at index 1 within class/object
  var lines =<< trim END
    vim9script

    class C
      var o0: list<list<number>> = [ [0],  [1],  [2]]
      var o1: list<list<number>> = [[10], [11], [12]]
      static var c0: list<list<number>> = [[20], [21], [22]]
      static var c1: list<list<number>> = [[30], [31], [32]]
    endclass

    def LockIt(arg: any)
      lockvar arg
    enddef

    def UnlockIt(arg: any)
      unlockvar arg
    enddef

    var obj = C.new()
    #lockvar obj.o1         # can't lock something you can't write to

    try
      lockvar obj.o1         # can't lock something you can't write to
      call assert_false(1, '"lockvar obj.o1" should have failed')
    catch
      call assert_exception('E1335:')
    endtry

    LockIt(obj.o1)         # but can lock it's value
    assert_equal(1, islocked("obj.o1"))
    assert_equal(1, islocked("obj.o1[0]"))
    assert_equal(1, islocked("obj.o1[1]"))
    UnlockIt(obj.o1)
    assert_equal(0, islocked("obj.o1"))
    assert_equal(0, islocked("obj.o1[0]"))

    lockvar obj.o1[0]
    assert_equal(0, islocked("obj.o1"))
    assert_equal(1, islocked("obj.o1[0]"))
    assert_equal(0, islocked("obj.o1[1]"))
    unlockvar obj.o1[0]
    assert_equal(0, islocked("obj.o1"))
    assert_equal(0, islocked("obj.o1[0]"))

    # Same thing, but with a static

    try
      lockvar C.c1         # can't lock something you can't write to
      call assert_false(1, '"lockvar C.c1" should have failed')
    catch
      call assert_exception('E1335:')
    endtry

    LockIt(C.c1)         # but can lock it's value
    assert_equal(1, islocked("C.c1"))
    assert_equal(1, islocked("C.c1[0]"))
    assert_equal(1, islocked("C.c1[1]"))
    UnlockIt(C.c1)
    assert_equal(0, islocked("C.c1"))
    assert_equal(0, islocked("C.c1[0]"))

    lockvar C.c1[0]
    assert_equal(0, islocked("C.c1"))
    assert_equal(1, islocked("C.c1[0]"))
    assert_equal(0, islocked("C.c1[1]"))
    unlockvar C.c1[0]
    assert_equal(0, islocked("C.c1"))
    assert_equal(0, islocked("C.c1[0]"))
  END
  v9.CheckSourceSuccess(lines)

  # Do islocked() from an object method
  # and then from a class method
  lines =<< trim END
    vim9script

    var l0o0 = [  [0],   [1],   [2]]
    var l0o1 = [ [10],  [11],  [12]]
    var l0c0 = [[120], [121], [122]]
    var l0c1 = [[130], [131], [132]]

    class C0
      var o0: list<list<number>> =   l0o0
      var o1: list<list<number>> =   l0o1
      static var c0: list<list<number>> = l0c0
      static var c1: list<list<number>> = l0c1
      def Islocked(arg: string): number
          return islocked(arg)
      enddef
      static def SIslocked(arg: string): number
        return islocked(arg)
      enddef
    endclass

    var l2o0 = [[20000], [20001], [20002]]
    var l2o1 = [[20010], [20011], [20012]]
    var l2c0 = [[20120], [20121], [20122]]
    var l2c1 = [[20130], [20131], [20132]]

    class C2
      var o0: list<list<number>> =   l2o0
      var o1: list<list<number>> =   l2o1
      static var c0: list<list<number>> = l2c0
      static var c1: list<list<number>> = l2c1
      def Islocked(arg: string): number
          return islocked(arg)
      enddef
      static def SIslocked(arg: string): number
        return islocked(arg)
      enddef
    endclass

    var obj0 = C0.new()
    var obj2 = C2.new()

    var l = [ obj0, null_object, obj2 ]

    # lock list, object func access through script var expr
    assert_equal(0, obj0.Islocked("l[0].o0"))
    assert_equal(0, obj0.Islocked("l[0].o0[2]"))
    lockvar l0o0
    assert_equal(1, obj0.Islocked("l[0].o0"))
    assert_equal(1, obj0.Islocked("l[0].o0[2]"))

    #echo "check-b" obj2.Islocked("l[1].o1")    # NULL OBJECT

    # lock list element, object func access through script var expr
    lockvar l0o1[1]
    assert_equal(0, obj0.Islocked("this.o1[0]"))
    assert_equal(1, obj0.Islocked("this.o1[1]"))

    assert_equal(0, obj0.Islocked("this.o1"))
    lockvar l0o1
    assert_equal(1, obj0.Islocked("this.o1"))
    unlockvar l0o1

    lockvar l0c1[1]

    # static by class name member expr from same class
    assert_equal(0, obj0.Islocked("C0.c1[0]"))
    assert_equal(1, obj0.Islocked("C0.c1[1]"))
    # static by bare name member expr from same class
    assert_equal(0, obj0.Islocked("c1[0]"))
    assert_equal(1, obj0.Islocked("c1[1]"))

    # static by class name member expr from other class
    assert_equal(0, obj2.Islocked("C0.c1[0]"))
    assert_equal(1, obj2.Islocked("C0.c1[1]"))
    # static by bare name member expr from other class
    assert_equal(0, obj2.Islocked("c1[0]"))
    assert_equal(0, obj2.Islocked("c1[1]"))


    # static by bare name in same class
    assert_equal(0, obj0.Islocked("c0"))
    lockvar l0c0
    assert_equal(1, obj0.Islocked("c0"))

    #
    # similar stuff, but use static method
    #

    unlockvar l0o0

    # lock list, object func access through script var expr
    assert_equal(0, C0.SIslocked("l[0].o0"))
    assert_equal(0, C0.SIslocked("l[0].o0[2]"))
    lockvar l0o0
    assert_equal(1, C0.SIslocked("l[0].o0"))
    assert_equal(1, C0.SIslocked("l[0].o0[2]"))

    unlockvar l0o1

    # can't access "this" from class method
    try
      C0.SIslocked("this.o1[0]")
      call assert_0(1, '"C0.SIslocked("this.o1[0]")" should have failed')
    catch
      call assert_exception('E121: Undefined variable: this')
    endtry

    lockvar l0c1[1]

    # static by class name member expr from same class
    assert_equal(0, C0.SIslocked("C0.c1[0]"))
    assert_equal(1, C0.SIslocked("C0.c1[1]"))
    # static by bare name member expr from same class
    assert_equal(0, C0.SIslocked("c1[0]"))
    assert_equal(1, C0.SIslocked("c1[1]"))

    # static by class name member expr from other class
    assert_equal(0, C2.SIslocked("C0.c1[0]"))
    assert_equal(1, C2.SIslocked("C0.c1[1]"))
    # static by bare name member expr from other class
    assert_equal(0, C2.SIslocked("c1[0]"))
    assert_equal(0, C2.SIslocked("c1[1]"))


    # static by bare name in same class
    unlockvar l0c0
    assert_equal(0, C0.SIslocked("c0"))
    lockvar l0c0
    assert_equal(1, C0.SIslocked("c0"))
  END
  v9.CheckSourceSuccess(lines)

  # Check islocked class/object from various places.
  lines =<< trim END
    vim9script

    class C
      def Islocked(arg: string): number
        return islocked(arg)
      enddef
      static def SIslocked(arg: string): number
        return islocked(arg)
      enddef
    endclass
    var obj = C.new()

    # object method
    assert_equal(0, obj.Islocked("this"))
    assert_equal(0, obj.Islocked("C"))

    # class method
    ### assert_equal(0, C.SIslocked("this"))
    assert_equal(0, C.SIslocked("C"))

    #script level
    var v: number
    v = islocked("C")
    assert_equal(0, v)
    v = islocked("obj")
    assert_equal(0, v)
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_lockvar_islocked_notfound()
  # Try non-existent things
  var lines =<< trim END
    vim9script

    class C
      def Islocked(arg: string): number
          return islocked(arg)
      enddef
      static def SIslocked(arg: string): number
        return islocked(arg)
      enddef
    endclass
    var obj = C.new()
    assert_equal(-1, obj.Islocked("anywhere"))
    assert_equal(-1, C.SIslocked("notanywhere"))
  END
  v9.CheckSourceSuccess(lines)

  # Something not found of the form "name1.name2" is an error
  lines =<< trim END
    vim9script

    islocked("one.two")
  END
  v9.CheckSourceFailure(lines, 'E121: Undefined variable: one')

  lines =<< trim END
    vim9script

    class C
      var val = { key: "value" }
      def Islocked(arg: string): number
          return islocked(arg)
      enddef
    endclass
    var obj = C.new()
    obj.Islocked("this.val.not_there"))
  END
  v9.CheckSourceFailure(lines, 'E716: Key not present in Dictionary: "not_there"')

  lines =<< trim END
    vim9script

    class C
      def Islocked(arg: string): number
          return islocked(arg)
      enddef
    endclass
    var obj = C.new()
    obj.Islocked("this.notobjmember")
  END
  v9.CheckSourceFailure(lines, 'E1326: Variable "notobjmember" not found in object "C"')

  # access a script variable through methods
  lines =<< trim END
    vim9script

    var l = [1]
    class C
      def Islocked(arg: string): number
          return islocked(arg)
      enddef
      static def SIslocked(arg: string): number
        return islocked(arg)
      enddef
    endclass
    var obj = C.new()
    assert_equal(0, obj.Islocked("l"))
    assert_equal(0, C.SIslocked("l"))
    lockvar l
    assert_equal(1, obj.Islocked("l"))
    assert_equal(1, C.SIslocked("l"))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for a protected object method
def Test_private_object_method()
  # Try calling a protected method using an object (at the script level)
  var lines =<< trim END
    vim9script

    class A
      def _Foo(): number
        return 1234
      enddef
    endclass
    var a = A.new()
    a._Foo()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 9)

  # Try calling a protected method using an object (from a def function)
  lines =<< trim END
    vim9script

    class A
      def _Foo(): number
        return 1234
      enddef
    endclass
    def T()
      var a = A.new()
      a._Foo()
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo()', 2)

  # Use a protected method from another object method (in script context)
  lines =<< trim END
    vim9script

    class A
      def _Foo(): number
        return 1234
      enddef
      def Bar(): number
        return this._Foo()
      enddef
    endclass
    var a = A.new()
    assert_equal(1234, a.Bar())
  END
  v9.CheckSourceSuccess(lines)

  # Use a protected method from another object method (def function context)
  lines =<< trim END
    vim9script

    class A
      def _Foo(): number
        return 1234
      enddef
      def Bar(): number
        return this._Foo()
      enddef
    endclass
    def T()
      var a = A.new()
      assert_equal(1234, a.Bar())
    enddef
    T()
  END
  v9.CheckSourceSuccess(lines)

  # Try calling a protected method without the "this" prefix
  lines =<< trim END
    vim9script

    class A
      def _Foo(): number
        return 1234
      enddef
      def Bar(): number
        return _Foo()
      enddef
    endclass
    var a = A.new()
    a.Bar()
  END
  v9.CheckSourceFailure(lines, 'E117: Unknown function: _Foo', 1)

  # Try calling a protected method using the class name
  lines =<< trim END
    vim9script

    class A
      def _Foo(): number
        return 1234
      enddef
    endclass
    A._Foo()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 8)

  # Define two protected methods with the same name
  lines =<< trim END
    vim9script

    class A
      def _Foo()
      enddef
      def _Foo()
      enddef
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: _Foo', 7)

  # Define a protected method and a object method with the same name
  lines =<< trim END
    vim9script

    class A
      def _Foo()
      enddef
      def Foo()
      enddef
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: Foo', 7)

  # Define an object method and a protected method with the same name
  lines =<< trim END
    vim9script

    class A
      def Foo()
      enddef
      def _Foo()
      enddef
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: _Foo', 7)

  # Call a public method and a protected method from a protected method
  lines =<< trim END
    vim9script

    class A
      def Foo(): number
        return 100
      enddef
      def _Bar(): number
        return 200
      enddef
      def _Baz()
        assert_equal(100, this.Foo())
        assert_equal(200, this._Bar())
      enddef
      def T()
        this._Baz()
      enddef
    endclass
    var a = A.new()
    a.T()
  END
  v9.CheckSourceSuccess(lines)

  # Try calling a protected method from another class
  lines =<< trim END
    vim9script

    class A
      def _Foo(): number
        return 100
      enddef
    endclass
    class B
      def Foo(): number
        var a = A.new()
        a._Foo()
      enddef
    endclass
    var b = B.new()
    b.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo()', 2)

  # Call a protected object method from a child class object method
  lines =<< trim END
    vim9script
    class A
      def _Foo(): number
        return 1234
      enddef
    endclass
    class B extends A
      def Bar()
      enddef
    endclass
    class C extends B
      def Baz(): number
        return this._Foo()
      enddef
    endclass
    var c = C.new()
    assert_equal(1234, c.Baz())
  END
  v9.CheckSourceSuccess(lines)

  # Call a protected object method from a child class object
  lines =<< trim END
    vim9script
    class A
      def _Foo(): number
        return 1234
      enddef
    endclass
    class B extends A
      def Bar()
      enddef
    endclass
    class C extends B
      def Baz(): number
      enddef
    endclass
    var c = C.new()
    assert_equal(1234, c._Foo())
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 16)

  # Using "_" prefix in a method name should fail outside of a class
  lines =<< trim END
    vim9script
    def _Foo(): number
      return 1234
    enddef
    var a = _Foo()
  END
  v9.CheckSourceFailure(lines, 'E1267: Function name must start with a capital: _Foo(): number', 2)
enddef

" Test for an protected class method
def Test_private_class_method()
  # Try calling a class protected method (at the script level)
  var lines =<< trim END
    vim9script

    class A
      static def _Foo(): number
        return 1234
      enddef
    endclass
    A._Foo()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 8)

  # Try calling a class protected method (from a def function)
  lines =<< trim END
    vim9script

    class A
      static def _Foo(): number
        return 1234
      enddef
    endclass
    def T()
      A._Foo()
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo()', 1)

  # Try calling a class protected method using an object (at the script level)
  lines =<< trim END
    vim9script

    class A
      static def _Foo(): number
        return 1234
      enddef
    endclass
    var a = A.new()
    a._Foo()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 9)

  # Try calling a class protected method using an object (from a def function)
  lines =<< trim END
    vim9script

    class A
      static def _Foo(): number
        return 1234
      enddef
    endclass
    def T()
      var a = A.new()
      a._Foo()
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 2)

  # Use a class protected method from an object method
  lines =<< trim END
    vim9script

    class A
      static def _Foo(): number
        return 1234
      enddef
      def Bar()
        assert_equal(1234, _Foo())
      enddef
    endclass
    var a = A.new()
    a.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Use a class protected method from another class protected method without the
  # class name prefix.
  lines =<< trim END
    vim9script

    class A
      static def _Foo1(): number
        return 1234
      enddef
      static def _Foo2()
        assert_equal(1234, _Foo1())
      enddef
      def Bar()
        _Foo2()
      enddef
    endclass
    var a = A.new()
    a.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Declare a class method and a class protected method with the same name
  lines =<< trim END
    vim9script

    class A
      static def _Foo()
      enddef
      static def Foo()
      enddef
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: Foo', 7)

  # Try calling a class protected method from another class
  lines =<< trim END
    vim9script

    class A
      static def _Foo(): number
        return 1234
      enddef
    endclass
    class B
      def Foo(): number
        return A._Foo()
      enddef
    endclass
    var b = B.new()
    assert_equal(1234, b.Foo())
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo()', 1)

  # Call a protected class method from a child class object method
  lines =<< trim END
    vim9script
    class A
      static def _Foo(): number
        return 1234
      enddef
    endclass
    class B extends A
      def Bar()
      enddef
    endclass
    class C extends B
      def Baz(): number
        return A._Foo()
      enddef
    endclass
    var c = C.new()
    assert_equal(1234, c.Baz())
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo()', 1)

  # Call a protected class method from a child class protected class method
  lines =<< trim END
    vim9script
    class A
      static def _Foo(): number
        return 1234
      enddef
    endclass
    class B extends A
      def Bar()
      enddef
    endclass
    class C extends B
      static def Baz(): number
        return A._Foo()
      enddef
    endclass
    assert_equal(1234, C.Baz())
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo()', 1)

  # Call a protected class method from a child class object
  lines =<< trim END
    vim9script
    class A
      static def _Foo(): number
        return 1234
      enddef
    endclass
    class B extends A
      def Bar()
      enddef
    endclass
    class C extends B
      def Baz(): number
      enddef
    endclass
    var c = C.new()
    assert_equal(1234, C._Foo())
  END
  v9.CheckSourceFailure(lines, 'E1325: Method "_Foo" not found in class "C"', 16)
enddef

" Test for using the return value of a class/object method as a function
" argument.
def Test_objmethod_funcarg()
  var lines =<< trim END
    vim9script

    class C
      def Foo(): string
        return 'foo'
      enddef
    endclass

    def Bar(a: number, s: string): string
      return s
    enddef

    def Baz(c: C)
      assert_equal('foo', Bar(10, c.Foo()))
    enddef

    var t = C.new()
    Baz(t)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class C
      static def Foo(): string
        return 'foo'
      enddef
    endclass

    def Bar(a: number, s: string): string
      return s
    enddef

    def Baz()
      assert_equal('foo', Bar(10, C.Foo()))
    enddef

    Baz()
  END
  v9.CheckSourceSuccess(lines)
enddef

def Test_static_inheritence()
  # subclasses get their own static copy
  var lines =<< trim END
    vim9script

    class A
      static var _svar: number
      var _mvar: number
      def new()
        _svar = 1
        this._mvar = 101
      enddef
      def AccessObject(): number
        return this._mvar
      enddef
      def AccessStaticThroughObject(): number
        return _svar
      enddef
    endclass

    class B extends A
      def new()
        this._mvar = 102
      enddef
    endclass

    class C extends B
      def new()
        this._mvar = 103
      enddef

      def AccessPrivateStaticThroughClassName(): number
        assert_equal(1, A._svar)
        return 444
      enddef
    endclass

    var oa = A.new()
    var ob = B.new()
    var oc = C.new()
    assert_equal(101, oa.AccessObject())
    assert_equal(102, ob.AccessObject())
    assert_equal(103, oc.AccessObject())

    assert_fails('echo oc.AccessPrivateStaticThroughClassName()', 'E1333: Cannot access protected variable "_svar" in class "A"')

    # verify object properly resolves to correct static
    assert_equal(1, oa.AccessStaticThroughObject())
    assert_equal(1, ob.AccessStaticThroughObject())
    assert_equal(1, oc.AccessStaticThroughObject())
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for declaring duplicate object and class members
def Test_dup_member_variable()
  # Duplicate member variable
  var lines =<< trim END
    vim9script
    class C
      var val = 10
      var val = 20
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: val', 4)

  # Duplicate protected member variable
  lines =<< trim END
    vim9script
    class C
      var _val = 10
      var _val = 20
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: _val', 4)

  # Duplicate public member variable
  lines =<< trim END
    vim9script
    class C
      public var val = 10
      public var val = 20
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: val', 4)

  # Duplicate protected member variable
  lines =<< trim END
    vim9script
    class C
      var val = 10
      var _val = 20
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: _val', 4)

  # Duplicate public and protected member variable
  lines =<< trim END
    vim9script
    class C
      var _val = 20
      public var val = 10
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: val', 4)

  # Duplicate class member variable
  lines =<< trim END
    vim9script
    class C
      static var s: string = "abc"
      static var _s: string = "def"
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: _s', 4)

  # Duplicate public and protected class member variable
  lines =<< trim END
    vim9script
    class C
      public static var s: string = "abc"
      static var _s: string = "def"
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: _s', 4)

  # Duplicate class and object member variable
  lines =<< trim END
    vim9script
    class C
      static var val = 10
      var val = 20
      def new()
      enddef
    endclass
    var c = C.new()
    assert_equal(10, C.val)
    assert_equal(20, c.val)
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: val', 4)

  # Duplicate object member variable in a derived class
  lines =<< trim END
    vim9script
    class A
      var val = 10
    endclass
    class B extends A
    endclass
    class C extends B
      var val = 20
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: val', 9)

  # Duplicate object protected member variable in a derived class
  lines =<< trim END
    vim9script
    class A
      var _val = 10
    endclass
    class B extends A
    endclass
    class C extends B
      var _val = 20
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: _val', 9)

  # Duplicate object protected member variable in a derived class
  lines =<< trim END
    vim9script
    class A
      var val = 10
    endclass
    class B extends A
    endclass
    class C extends B
      var _val = 20
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: _val', 9)

  # Duplicate object member variable in a derived class
  lines =<< trim END
    vim9script
    class A
      var _val = 10
    endclass
    class B extends A
    endclass
    class C extends B
      var val = 20
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: val', 9)

  # Two member variables with a common prefix
  lines =<< trim END
    vim9script
    class A
      public static var svar2: number
      public static var svar: number
    endclass
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for accessing a protected member outside a class in a def function
def Test_private_member_access_outside_class()
  # protected object member variable
  var lines =<< trim END
    vim9script
    class A
      var _val = 10
      def GetVal(): number
        return this._val
      enddef
    endclass
    def T()
      var a = A.new()
      a._val = 20
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_val" in class "A"', 2)

  # access a non-existing protected object member variable
  lines =<< trim END
    vim9script
    class A
      var _val = 10
    endclass
    def T()
      var a = A.new()
      a._a = 1
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1326: Variable "_a" not found in object "A"', 2)

  # protected static member variable
  lines =<< trim END
    vim9script
    class A
      static var _val = 10
    endclass
    def T()
      var a = A.new()
      var x = a._val
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "_val" accessible only using class "A"', 2)

  # protected static member variable
  lines =<< trim END
    vim9script
    class A
      static var _val = 10
    endclass
    def T()
      var a = A.new()
      a._val = 3
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "_val" accessible only using class "A"', 2)

  # protected static class variable
  lines =<< trim END
    vim9script
    class A
      static var _val = 10
    endclass
    def T()
      var x = A._val
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_val" in class "A"', 1)

  # protected static class variable
  lines =<< trim END
    vim9script
    class A
      static var _val = 10
    endclass
    def T()
      A._val = 3
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1333: Cannot access protected variable "_val" in class "A"', 1)
enddef

" Test for changing the member access of an interface in a implementation class
def Test_change_interface_member_access()
  var lines =<< trim END
    vim9script
    interface A
      var val: number
    endinterface
    class B implements A
      public var val = 10
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1367: Access level of variable "val" of interface "A" is different', 7)

  lines =<< trim END
    vim9script
    interface A
      var val: number
    endinterface
    class B implements A
      public var val = 10
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1367: Access level of variable "val" of interface "A" is different', 7)
enddef

" Test for trying to change a readonly member from a def function
def Test_readonly_member_change_in_def_func()
  var lines =<< trim END
    vim9script
    class A
      var val: number
    endclass
    def T()
      var a = A.new()
      a.val = 20
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1335: Variable "val" in class "A" is not writable', 2)
enddef

" Test for reading and writing a class member from a def function
def Test_modify_class_member_from_def_function()
  var lines =<< trim END
    vim9script
    class A
      var var1: number = 10
      public static var var2: list<number> = [1, 2]
      public static var var3: dict<number> = {a: 1, b: 2}
      static var _priv_var4: number = 40
    endclass
    def T()
      assert_equal([1, 2], A.var2)
      assert_equal({a: 1, b: 2}, A.var3)
      A.var2 = [3, 4]
      A.var3 = {c: 3, d: 4}
      assert_equal([3, 4], A.var2)
      assert_equal({c: 3, d: 4}, A.var3)
      assert_fails('echo A._priv_var4', 'E1333: Cannot access protected variable "_priv_var4" in class "A"')
    enddef
    T()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for accessing a class member variable using an object
def Test_class_variable_access_using_object()
  var lines =<< trim END
    vim9script
    class A
      public static var svar1: list<number> = [1]
      public static var svar2: list<number> = [2]
    endclass

    A.svar1->add(3)
    A.svar2->add(4)
    assert_equal([1, 3], A.svar1)
    assert_equal([2, 4], A.svar2)

    def Foo()
      A.svar1->add(7)
      A.svar2->add(8)
      assert_equal([1, 3, 7], A.svar1)
      assert_equal([2, 4, 8], A.svar2)
    enddef
    Foo()
  END
  v9.CheckSourceSuccess(lines)

  # Cannot read from a class variable using an object in script context
  lines =<< trim END
    vim9script
    class A
      public var var1: number
      public static var svar2: list<number> = [1]
    endclass

    var a = A.new()
    echo a.svar2
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "svar2" accessible only using class "A"', 8)

  # Cannot write to a class variable using an object in script context
  lines =<< trim END
    vim9script
    class A
      public var var1: number
      public static var svar2: list<number> = [1]
    endclass

    var a = A.new()
    a.svar2 = [2]
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "svar2" accessible only using class "A"', 8)

  # Cannot read from a class variable using an object in def method context
  lines =<< trim END
    vim9script
    class A
      public var var1: number
      public static var svar2: list<number> = [1]
    endclass

    def T()
      var a = A.new()
      echo a.svar2
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "svar2" accessible only using class "A"', 2)

  # Cannot write to a class variable using an object in def method context
  lines =<< trim END
    vim9script
    class A
      public var var1: number
      public static var svar2: list<number> = [1]
    endclass

    def T()
      var a = A.new()
      a.svar2 = [2]
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "svar2" accessible only using class "A"', 2)
enddef

" Test for using a interface method using a child object
def Test_interface_method_from_child()
  var lines =<< trim END
    vim9script

    interface A
      def Foo(): string
    endinterface

    class B implements A
      def Foo(): string
        return 'foo'
      enddef
    endclass

    class C extends B
      def Bar(): string
        return 'bar'
      enddef
    endclass

    def T1(a: A)
      assert_equal('foo', a.Foo())
    enddef

    def T2(b: B)
      assert_equal('foo', b.Foo())
    enddef

    var c = C.new()
    T1(c)
    T2(c)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using an interface method using a child object when it is overridden
" by the child class.
def Test_interface_overridden_method_from_child()
  var lines =<< trim END
    vim9script

    interface A
      def Foo(): string
    endinterface

    class B implements A
      def Foo(): string
        return 'b-foo'
      enddef
    endclass

    class C extends B
      def Bar(): string
        return 'bar'
      enddef
      def Foo(): string
        return 'c-foo'
      enddef
    endclass

    def T1(a: A)
      assert_equal('c-foo', a.Foo())
    enddef

    def T2(b: B)
      assert_equal('c-foo', b.Foo())
    enddef

    var c = C.new()
    T1(c)
    T2(c)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for interface inheritance
def Test_interface_inheritance()
  var lines =<< trim END
    vim9script

    interface A
      def A_Fn(): string
    endinterface

    interface B
      def B_Fn(): string
    endinterface

    interface C
      def C_Fn(): string
    endinterface

    class C1 implements A
      def A_Fn(): string
        return 'c1-a'
      enddef
    endclass

    class C2 extends C1 implements B
      def B_Fn(): string
        return 'c2-b'
      enddef
      def A_Fn(): string
        return 'c2-a'
      enddef
    endclass

    class C3 extends C2 implements C
      def C_Fn(): string
        return 'c3-c'
      enddef
      def A_Fn(): string
        return 'c3-a'
      enddef
      def B_Fn(): string
        return 'c3-b'
      enddef
    endclass

    def T1(a: A, s: string)
      assert_equal(s, a.A_Fn())
    enddef

    def T2(b: B, s: string)
      assert_equal(s, b.B_Fn())
    enddef

    def T3(c: C, s: string)
      assert_equal(s, c.C_Fn())
    enddef

    def T4(c1: C1)
      T1(c1, 'c3-a')
    enddef

    def T5(c2: C2)
      T1(c2, 'c3-a')
      T2(c2, 'c3-b')
    enddef

    def T6(c3: C3)
      T1(c3, 'c3-a')
      T2(c3, 'c3-b')
      T3(c3, 'c3-c')
    enddef

    var o3 = C3.new()
    T4(o3)
    T5(o3)
    T6(o3)
  END
  v9.CheckSourceSuccess(lines)

  # Both the parent and child classes implement the same interface
  lines =<< trim END
    vim9script

    interface I
      def Foo(): string
    endinterface

    class A implements I
      def Foo(): string
        return 'A-foo'
      enddef
    endclass

    class B implements I
      def Foo(): string
        return 'B-foo'
      enddef
    endclass

    def Bar(i1: I): string
      return i1.Foo()
    enddef

    var b = B.new()
    assert_equal('B-foo', Bar(b))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for abstract methods
def Test_abstract_method()
  # Use two abstract methods
  var lines =<< trim END
    vim9script
    abstract class A
      def M1(): number
        return 10
      enddef
      abstract def M2(): number
      abstract def M3(): number
    endclass
    class B extends A
      def M2(): number
        return 20
      enddef
      def M3(): number
        return 30
      enddef
    endclass
    var b = B.new()
    assert_equal([10, 20, 30], [b.M1(), b.M2(), b.M3()])
  END
  v9.CheckSourceSuccess(lines)

  # Don't define an abstract method
  lines =<< trim END
    vim9script
    abstract class A
      abstract def Foo()
    endclass
    class B extends A
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1373: Abstract method "Foo" is not implemented', 6)

  # Use abstract method in a concrete class
  lines =<< trim END
    vim9script
    class A
      abstract def Foo()
    endclass
    class B extends A
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1372: Abstract method "abstract def Foo()" cannot be defined in a concrete class', 3)

  # Use abstract method in an interface
  lines =<< trim END
    vim9script
    interface A
      abstract def Foo()
    endinterface
    class B implements A
      def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1404: Abstract cannot be used in an interface', 3)

  # Use abstract static method in an interface
  lines =<< trim END
    vim9script
    interface A
      abstract static def Foo()
      enddef
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1404: Abstract cannot be used in an interface', 3)

  # Use abstract static variable in an interface
  lines =<< trim END
    vim9script
    interface A
      abstract static foo: number = 10
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1404: Abstract cannot be used in an interface', 3)

  # Abbreviate the "abstract" keyword
  lines =<< trim END
    vim9script
    class A
      abs def Foo()
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1065: Command cannot be shortened: abs def Foo()', 3)

  # Use "abstract" with a member variable
  lines =<< trim END
    vim9script
    abstract class A
      abstract this.val = 10
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1371: Abstract must be followed by "def"', 3)

  # Use a static abstract method
  lines =<< trim END
    vim9script
    abstract class A
      abstract static def Foo(): number
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1371: Abstract must be followed by "def"', 3)

  # Type mismatch between abstract method and concrete method
  lines =<< trim END
    vim9script
    abstract class A
      abstract def Foo(a: string, b: number): list<number>
    endclass
    class B extends A
      def Foo(a: number, b: string): list<string>
        return []
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1383: Method "Foo": type mismatch, expected func(string, number): list<number> but got func(number, string): list<string>', 9)

  # Invoke an abstract method from a def function
  lines =<< trim END
    vim9script
    abstract class A
      abstract def Foo(): list<number>
    endclass
    class B extends A
      def Foo(): list<number>
        return [3, 5]
      enddef
    endclass
    def Bar(c: B)
      assert_equal([3, 5], c.Foo())
    enddef
    var b = B.new()
    Bar(b)
  END
  v9.CheckSourceSuccess(lines)

  # Use a static method in an abstract class
  lines =<< trim END
    vim9script
    abstract class A
      static def Foo(): string
        return 'foo'
      enddef
    endclass
    assert_equal('foo', A.Foo())
  END
  v9.CheckSourceSuccess(lines)

    # Invoke method returning a value through the abstract class. See #15432.
  lines =<< trim END
    vim9script

    abstract class A
        abstract def String(): string
    endclass

    class B extends A
        def String(): string
            return 'B'
        enddef
    endclass

    def F(o: A)
        assert_equal('B', o.String())
    enddef
    F(B.new())
  END
  v9.CheckSourceSuccess(lines)

  # Invoke abstract method returning a value does not compile
  lines =<< trim END
    vim9script

    abstract class A
      abstract def String(): string
        return 'X'
      enddef
    endclass
  END
  v9.CheckScriptFailure(lines, "E1318: Not a valid command in a class: return 'X'")
enddef

" Test for calling a class method from a subclass
def Test_class_method_call_from_subclass()
  # class method call from a subclass
  var lines =<< trim END
    vim9script

    class A
      static def Foo()
        echo "foo"
      enddef
    endclass

    class B extends A
      def Bar()
        Foo()
      enddef
    endclass

    var b = B.new()
    b.Bar()
  END
  v9.CheckSourceFailure(lines, 'E1384: Class method "Foo" accessible only inside class "A"', 1)
enddef

" Test for calling a class method using an object in a def function context and
" script context.
def Test_class_method_call_using_object()
  # script context
  var lines =<< trim END
    vim9script
    class A
      static def Foo(): list<string>
        return ['a', 'b']
      enddef
      def Bar()
        assert_equal(['a', 'b'], A.Foo())
        assert_equal(['a', 'b'], Foo())
      enddef
    endclass

    def T()
      assert_equal(['a', 'b'], A.Foo())
      var t_a = A.new()
      t_a.Bar()
    enddef

    assert_equal(['a', 'b'], A.Foo())
    var a = A.new()
    a.Bar()
    T()
  END
  v9.CheckSourceSuccess(lines)

  # script context
  lines =<< trim END
    vim9script
    class A
      static def Foo(): string
        return 'foo'
      enddef
    endclass

    var a = A.new()
    assert_equal('foo', a.Foo())
  END
  v9.CheckSourceFailure(lines, 'E1385: Class method "Foo" accessible only using class "A"', 9)

  # def function context
  lines =<< trim END
    vim9script
    class A
      static def Foo(): string
        return 'foo'
      enddef
    endclass

    def T()
      var a = A.new()
      assert_equal('foo', a.Foo())
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1385: Class method "Foo" accessible only using class "A"', 2)
enddef

def Test_class_variable()
  var lines =<< trim END
    vim9script

    class A
      public static var val: number = 10
      static def ClassFunc()
        assert_equal(10, val)
      enddef
      def ObjFunc()
        assert_equal(10, val)
      enddef
    endclass

    class B extends A
    endclass

    assert_equal(10, A.val)
    A.ClassFunc()
    var a = A.new()
    a.ObjFunc()
    var b = B.new()
    b.ObjFunc()

    def T1(a1: A)
      a1.ObjFunc()
      A.ClassFunc()
    enddef
    T1(b)

    A.val = 20
    assert_equal(20, A.val)
  END
  v9.CheckSourceSuccess(lines)

  # Modifying a parent class variable from a child class method
  lines =<< trim END
    vim9script

    class A
      static var val: number = 10
    endclass

    class B extends A
      static def ClassFunc()
        val = 20
      enddef
    endclass
    B.ClassFunc()
  END
  v9.CheckSourceFailure(lines, 'E1374: Class variable "val" accessible only inside class "A"', 1)

  # Reading a parent class variable from a child class method
  lines =<< trim END
    vim9script

    class A
      static var val: number = 10
    endclass

    class B extends A
      static def ClassFunc()
        var i = val
      enddef
    endclass
    B.ClassFunc()
  END
  v9.CheckSourceFailure(lines, 'E1374: Class variable "val" accessible only inside class "A"', 1)

  # Modifying a parent class variable from a child object method
  lines =<< trim END
    vim9script

    class A
      static var val: number = 10
    endclass

    class B extends A
      def ObjFunc()
        val = 20
      enddef
    endclass
    var b = B.new()
    b.ObjFunc()
  END
  v9.CheckSourceFailure(lines, 'E1374: Class variable "val" accessible only inside class "A"', 1)

  # Reading a parent class variable from a child object method
  lines =<< trim END
    vim9script

    class A
      static var val: number = 10
    endclass

    class B extends A
      def ObjFunc()
        var i = val
      enddef
    endclass
    var b = B.new()
    b.ObjFunc()
  END
  v9.CheckSourceFailure(lines, 'E1374: Class variable "val" accessible only inside class "A"', 1)

  # Modifying a class variable using an object at script level
  lines =<< trim END
    vim9script

    class A
      static var val: number = 10
    endclass
    var a = A.new()
    a.val = 20
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "val" accessible only using class "A"', 7)

  # Reading a class variable using an object at script level
  lines =<< trim END
    vim9script

    class A
      static var val: number = 10
    endclass
    var a = A.new()
    var i = a.val
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "val" accessible only using class "A"', 7)

  # Modifying a class variable using an object at function level
  lines =<< trim END
    vim9script

    class A
      static var val: number = 10
    endclass

    def T()
      var a = A.new()
      a.val = 20
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "val" accessible only using class "A"', 2)

  # Reading a class variable using an object at function level
  lines =<< trim END
    vim9script

    class A
      static var val: number = 10
    endclass
    def T()
      var a = A.new()
      var i = a.val
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1375: Class variable "val" accessible only using class "A"', 2)

  # Use old implicit var declaration syntax (without initialization)
  lines =<< trim END
    vim9script

    class A
      static val: number
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1368: Static must be followed by "var" or "def"', 4)

  # Use old implicit var declaration syntax (with initialization)
  lines =<< trim END
    vim9script

    class A
      static val: number = 10
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1368: Static must be followed by "var" or "def"', 4)

  # Use old implicit var declaration syntax (type inferred)
  lines =<< trim END
    vim9script

    class A
      static val = 10
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1368: Static must be followed by "var" or "def"', 4)

  # Missing ":var" in "var" class variable declaration (without initialization)
  lines =<< trim END
    vim9script

    class A
      static var: number
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1329: Invalid class variable declaration: static var: number', 4)

  # Missing ":var" in "var" class variable declaration (with initialization)
  lines =<< trim END
    vim9script

    class A
      static var: number = 10
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1329: Invalid class variable declaration: static var: number = 10', 4)

  # Missing ":var" in "var" class variable declaration (type inferred)
  lines =<< trim END
    vim9script

    class A
      static var = 10
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1329: Invalid class variable declaration: static var = 10', 4)

enddef

" Test for using a duplicate class method and class variable in a child class
def Test_dup_class_member()
  # duplicate class variable, class method and overridden object method
  var lines =<< trim END
    vim9script
    class A
      static var sval = 100
      static def Check()
        assert_equal(100, sval)
      enddef
      def GetVal(): number
        return sval
      enddef
    endclass

    class B extends A
      static var sval = 200
      static def Check()
        assert_equal(200, sval)
      enddef
      def GetVal(): number
        return sval
      enddef
    endclass

    def T1(aa: A): number
      return aa.GetVal()
    enddef

    def T2(bb: B): number
      return bb.GetVal()
    enddef

    assert_equal(100, A.sval)
    assert_equal(200, B.sval)
    var a = A.new()
    assert_equal(100, a.GetVal())
    var b = B.new()
    assert_equal(200, b.GetVal())
    assert_equal(200, T1(b))
    assert_equal(200, T2(b))
  END
  v9.CheckSourceSuccess(lines)

  # duplicate class variable and class method
  lines =<< trim END
    vim9script
    class A
      static var sval = 100
      static def Check()
        assert_equal(100, sval)
      enddef
      def GetVal(): number
        return sval
      enddef
    endclass

    class B extends A
      static var sval = 200
      static def Check()
        assert_equal(200, sval)
      enddef
    endclass

    def T1(aa: A): number
      return aa.GetVal()
    enddef

    def T2(bb: B): number
      return bb.GetVal()
    enddef

    assert_equal(100, A.sval)
    assert_equal(200, B.sval)
    var a = A.new()
    assert_equal(100, a.GetVal())
    var b = B.new()
    assert_equal(100, b.GetVal())
    assert_equal(100, T1(b))
    assert_equal(100, T2(b))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for calling an instance method using the class
def Test_instance_method_call_using_class()
  # Invoke an object method using a class in script context
  var lines =<< trim END
    vim9script
    class A
      def Foo()
        echo "foo"
      enddef
    endclass
    A.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1386: Object method "Foo" accessible only using class "A" object', 7)

  # Invoke an object method using a class in def function context
  lines =<< trim END
    vim9script
    class A
      def Foo()
        echo "foo"
      enddef
    endclass
    def T()
      A.Foo()
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1386: Object method "Foo" accessible only using class "A" object', 1)
enddef

" Test for duplicate class method and instance method
def Test_dup_classmethod_objmethod()
  # Duplicate instance method
  var lines =<< trim END
    vim9script
    class A
      static def Foo()
      enddef
      def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: Foo', 6)

  # Duplicate protected instance method
  lines =<< trim END
    vim9script
    class A
      static def Foo()
      enddef
      def _Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: _Foo', 6)

  # Duplicate class method
  lines =<< trim END
    vim9script
    class A
      def Foo()
      enddef
      static def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: Foo', 6)

  # Duplicate protected class method
  lines =<< trim END
    vim9script
    class A
      def Foo()
      enddef
      static def _Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: _Foo', 6)

  # Duplicate protected class and object method
  lines =<< trim END
    vim9script
    class A
      def _Foo()
      enddef
      static def _Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1355: Duplicate function: _Foo', 6)
enddef

" Test for an instance method access level comparison with parent instance
" methods.
def Test_instance_method_access_level()
  # protected method in subclass
  var lines =<< trim END
    vim9script
    class A
      def Foo()
      enddef
    endclass
    class B extends A
    endclass
    class C extends B
      def _Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1377: Access level of method "_Foo" is different in class "A"', 11)

  # Public method in subclass
  lines =<< trim END
    vim9script
    class A
      def _Foo()
      enddef
    endclass
    class B extends A
    endclass
    class C extends B
      def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1377: Access level of method "Foo" is different in class "A"', 11)
enddef

def Test_extend_empty_class()
  var lines =<< trim END
    vim9script
    class A
    endclass
    class B extends A
    endclass
    class C extends B
      public static var rw_class_var = 1
      public var rw_obj_var = 2
      static def ClassMethod(): number
        return 3
      enddef
      def ObjMethod(): number
        return 4
      enddef
    endclass
    assert_equal(1, C.rw_class_var)
    assert_equal(3, C.ClassMethod())
    var c = C.new()
    assert_equal(2, c.rw_obj_var)
    assert_equal(4, c.ObjMethod())
  END
  v9.CheckSourceSuccess(lines)
enddef

" A interface cannot have a static variable or a static method or a private
" variable or a protected method or a public variable
def Test_interface_with_unsupported_members()
  var lines =<< trim END
    vim9script
    interface A
      static var num: number
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface', 3)

  lines =<< trim END
    vim9script
    interface A
      static var _num: number
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface', 3)

  lines =<< trim END
    vim9script
    interface A
      public static var num: number
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1387: public variable not supported in an interface', 3)

  lines =<< trim END
    vim9script
    interface A
      public static var num: number
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1387: public variable not supported in an interface', 3)

  lines =<< trim END
    vim9script
    interface A
      static var _num: number
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface', 3)

  lines =<< trim END
    vim9script
    interface A
      static def Foo(d: dict<any>): list<string>
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface', 3)

  lines =<< trim END
    vim9script
    interface A
      static def _Foo(d: dict<any>): list<string>
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface', 3)

  lines =<< trim END
    vim9script
    interface A
      var _Foo: list<string>
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1379: Protected variable not supported in an interface', 3)

  lines =<< trim END
    vim9script
    interface A
      def _Foo(d: dict<any>): list<string>
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1380: Protected method not supported in an interface', 3)
enddef

" Test for extending an interface
def Test_extend_interface()
  var lines =<< trim END
    vim9script
    interface A
      var var1: list<string>
      def Foo()
    endinterface
    interface B extends A
      var var2: dict<string>
      def Bar()
    endinterface
    class C implements A, B
      var var1 = [1, 2]
      def Foo()
      enddef
      var var2 = {a: '1'}
      def Bar()
      enddef
    endclass
  END
  v9.CheckSourceSuccess(lines)

  # extending empty interface
  lines =<< trim END
    vim9script
    interface A
    endinterface
    interface B extends A
    endinterface
    class C implements B
    endclass
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    interface A
      def Foo()
    endinterface
    interface B extends A
      var var2: dict<string>
    endinterface
    class C implements A, B
      var var2 = {a: '1'}
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1349: Method "Foo" of interface "A" is not implemented', 10)

  lines =<< trim END
    vim9script
    interface A
      def Foo()
    endinterface
    interface B extends A
      var var2: dict<string>
    endinterface
    class C implements A, B
      def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1348: Variable "var2" of interface "B" is not implemented', 11)

  # interface cannot extend a class
  lines =<< trim END
    vim9script
    class A
    endclass
    interface B extends A
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1354: Cannot extend A', 5)

  # class cannot extend an interface
  lines =<< trim END
    vim9script
    interface A
    endinterface
    class B extends A
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1354: Cannot extend A', 5)

  # interface cannot implement another interface
  lines =<< trim END
    vim9script
    interface A
    endinterface
    interface B implements A
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1381: Interface cannot use "implements"', 4)

  # interface cannot extend multiple interfaces
  lines =<< trim END
    vim9script
    interface A
    endinterface
    interface B
    endinterface
    interface C extends A, B
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1315: White space required after name: A, B', 6)

  # Variable type in an extended interface is of different type
  lines =<< trim END
    vim9script
    interface A
      var val1: number
    endinterface
    interface B extends A
      var val2: string
    endinterface
    interface C extends B
      var val1: string
      var val2: number
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1382: Variable "val1": type mismatch, expected number but got string', 11)
enddef

" Test for a child class implementing an interface when some of the methods are
" defined in the parent class.
def Test_child_class_implements_interface()
  var lines =<< trim END
    vim9script

    interface Intf
      def F1(): list<list<number>>
      def F2(): list<list<number>>
      def F3(): list<list<number>>
      var var1: list<dict<number>>
      var var2: list<dict<number>>
      var var3: list<dict<number>>
    endinterface

    class A
      def A1()
      enddef
      def F3(): list<list<number>>
        return [[3]]
      enddef
      var v1: list<list<number>> = [[0]]
      var var3 = [{c: 30}]
    endclass

    class B extends A
      def B1()
      enddef
      def F2(): list<list<number>>
        return [[2]]
      enddef
      var v2: list<list<number>> = [[0]]
      var var2 = [{b: 20}]
    endclass

    class C extends B implements Intf
      def C1()
      enddef
      def F1(): list<list<number>>
        return [[1]]
      enddef
      var v3: list<list<number>> = [[0]]
      var var1 = [{a: 10}]
    endclass

    def T(if: Intf)
      assert_equal([[1]], if.F1())
      assert_equal([[2]], if.F2())
      assert_equal([[3]], if.F3())
      assert_equal([{a: 10}], if.var1)
      assert_equal([{b: 20}], if.var2)
      assert_equal([{c: 30}], if.var3)
    enddef

    var c = C.new()
    T(c)
    assert_equal([[1]], c.F1())
    assert_equal([[2]], c.F2())
    assert_equal([[3]], c.F3())
    assert_equal([{a: 10}], c.var1)
    assert_equal([{b: 20}], c.var2)
    assert_equal([{c: 30}], c.var3)
  END
  v9.CheckSourceSuccess(lines)

  # One of the interface methods is not found
  lines =<< trim END
    vim9script

    interface Intf
      def F1()
      def F2()
      def F3()
    endinterface

    class A
      def A1()
      enddef
    endclass

    class B extends A
      def B1()
      enddef
      def F2()
      enddef
    endclass

    class C extends B implements Intf
      def C1()
      enddef
      def F1()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1349: Method "F3" of interface "Intf" is not implemented', 26)

  # One of the interface methods is of different type
  lines =<< trim END
    vim9script

    interface Intf
      def F1()
      def F2()
      def F3()
    endinterface

    class A
      def F3(): number
        return 0
      enddef
      def A1()
      enddef
    endclass

    class B extends A
      def B1()
      enddef
      def F2()
      enddef
    endclass

    class C extends B implements Intf
      def C1()
      enddef
      def F1()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1383: Method "F3": type mismatch, expected func() but got func(): number', 29)

  # One of the interface variables is not present
  lines =<< trim END
    vim9script

    interface Intf
      var var1: list<dict<number>>
      var var2: list<dict<number>>
      var var3: list<dict<number>>
    endinterface

    class A
      var v1: list<list<number>> = [[0]]
    endclass

    class B extends A
      var v2: list<list<number>> = [[0]]
      var var2 = [{b: 20}]
    endclass

    class C extends B implements Intf
      var v3: list<list<number>> = [[0]]
      var var1 = [{a: 10}]
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1348: Variable "var3" of interface "Intf" is not implemented', 21)

  # One of the interface variables is of different type
  lines =<< trim END
    vim9script

    interface Intf
      var var1: list<dict<number>>
      var var2: list<dict<number>>
      var var3: list<dict<number>>
    endinterface

    class A
      var v1: list<list<number>> = [[0]]
      var var3: list<dict<string>>
    endclass

    class B extends A
      var v2: list<list<number>> = [[0]]
      var var2 = [{b: 20}]
    endclass

    class C extends B implements Intf
      var v3: list<list<number>> = [[0]]
      var var1 = [{a: 10}]
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1382: Variable "var3": type mismatch, expected list<dict<number>> but got list<dict<string>>', 22)
enddef

" Test for extending an interface with duplicate variables and methods
def Test_interface_extends_with_dup_members()
  var lines =<< trim END
    vim9script
    interface A
      var n1: number
      def Foo1(): number
    endinterface
    interface B extends A
      var n2: number
      var n1: number
      def Foo2(): number
      def Foo1(): number
    endinterface
    class C implements B
      var n1 = 10
      var n2 = 20
      def Foo1(): number
        return 30
      enddef
      def Foo2(): number
        return 40
      enddef
    endclass
    def T1(a: A)
      assert_equal(10, a.n1)
      assert_equal(30, a.Foo1())
    enddef
    def T2(b: B)
      assert_equal(10, b.n1)
      assert_equal(20, b.n2)
      assert_equal(30, b.Foo1())
      assert_equal(40, b.Foo2())
    enddef
    var c = C.new()
    T1(c)
    T2(c)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for implementing an interface with different ordering for the interface
" member variables.
def Test_implement_interface_with_different_variable_order()
  var lines =<< trim END
    vim9script

    interface IX
      var F: func(): string
    endinterface

    class X implements IX
      var x: number
      var F: func(): string = () => 'ok'
    endclass

    def Foo(ix: IX): string
      return ix.F()
    enddef

    var x0 = X.new(0)
    assert_equal('ok', Foo(x0))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for inheriting interfaces from an imported super class
def Test_interface_inheritance_with_imported_super()
  var lines =<< trim END
    vim9script

    export interface I
      def F(): string
    endinterface

    export class A implements I
      def F(): string
        return 'A'
      enddef
    endclass
  END
  writefile(lines, 'Xinheritintfimportclass.vim', 'D')

  lines =<< trim END
    vim9script

    import './Xinheritintfimportclass.vim' as i_imp

    # class C extends i_imp.A
    class C extends i_imp.A implements i_imp.I
      def F(): string
        return 'C'
      enddef
    endclass

    def TestI(i: i_imp.I): string
        return i.F()
    enddef

    assert_equal('C', TestI(C.new()))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using "any" type for a variable in a sub-class while it has a
" concrete type in the interface
def Test_implements_using_var_type_any()
  var lines =<< trim END
    vim9script
    interface A
      var val: list<dict<string>>
    endinterface
    class B implements A
      var val = [{a: '1'}, {b: '2'}]
    endclass
    var b = B.new()
    assert_equal([{a: '1'}, {b: '2'}], b.val)
  END
  v9.CheckSourceSuccess(lines)

  # initialize instance variable using a different type
  lines =<< trim END
    vim9script
    interface A
      var val: list<dict<string>>
    endinterface
    class B implements A
      var val = {a: 1, b: 2}
    endclass
    var b = B.new()
  END
  v9.CheckSourceFailure(lines, 'E1382: Variable "val": type mismatch, expected list<dict<string>> but got dict<number>', 1)
enddef

" Test for assigning to a member variable in a nested class
def Test_nested_object_assignment()
  var lines =<< trim END
    vim9script

    class A
      var value: number
    endclass

    class B
      var a: A = A.new()
    endclass

    class C
      var b: B = B.new()
    endclass

    class D
      var c: C = C.new()
    endclass

    def T(da: D)
      da.c.b.a.value = 10
    enddef

    var d = D.new()
    T(d)
  END
  v9.CheckSourceFailure(lines, 'E1335: Variable "value" in class "A" is not writable', 1)
enddef

" Test for calling methods using a null object
def Test_null_object_method_call()
  # Calling a object method using a null object in script context
  var lines =<< trim END
    vim9script

    class C
      def Foo()
        assert_report('This method should not be executed')
      enddef
    endclass

    var o: C
    o.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 10)

  # Calling a object method using a null object in def function context
  lines =<< trim END
    vim9script

    class C
      def Foo()
        assert_report('This method should not be executed')
      enddef
    endclass

    def T()
      var o: C
      o.Foo()
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 2)

  # Calling a object method through another class method using a null object in
  # script context
  lines =<< trim END
    vim9script

    class C
      def Foo()
        assert_report('This method should not be executed')
      enddef

      static def Bar(o_any: any)
        var o_typed: C = o_any
        o_typed.Foo()
      enddef
    endclass

    var o: C
    C.Bar(o)
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 2)

  # Calling a object method through another class method using a null object in
  # def function context
  lines =<< trim END
    vim9script

    class C
      def Foo()
        assert_report('This method should not be executed')
      enddef

      static def Bar(o_any: any)
        var o_typed: C = o_any
        o_typed.Foo()
      enddef
    endclass

    def T()
      var o: C
      C.Bar(o)
    enddef
    T()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 2)

  # Calling an object method defined in a class that is extended. This differs
  # from the previous by invoking ISN_METHODCALL instead of ISN_DCALL.
  lines =<< trim END
    vim9script

    class C0
      def F()
      enddef
    endclass

    class C extends C0
    endclass

    def X()
      var o: C0 = null_object
      o.F()
    enddef
    X()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 2)

  # Getting a function ref an object method.
  lines =<< trim END
    vim9script

    class C0
      def F()
      enddef
    endclass

    class C extends C0
    endclass

    def X()
      var o: C0 = null_object
      var XXX = o.F
    enddef
    X()
  END
  v9.CheckSourceFailure(lines, 'E1360: Using a null object', 2)
enddef

" Test for using a dict as an object member
def Test_dict_object_member()
  var lines =<< trim END
    vim9script

    class Context
      public var state: dict<number> = {}
      def GetState(): dict<number>
        return this.state
      enddef
    endclass

    var ctx = Context.new()
    ctx.state->extend({a: 1})
    ctx.state['b'] = 2
    assert_equal({a: 1, b: 2}, ctx.GetState())

    def F()
      ctx.state['c'] = 3
      assert_equal({a: 1, b: 2, c: 3}, ctx.GetState())
    enddef
    F()
    assert_equal(3, ctx.state.c)
    ctx.state.c = 4
    assert_equal(4, ctx.state.c)
  END
  v9.CheckSourceSuccess(lines)
enddef

" The following test was failing after 9.0.1914.  This was caused by using a
" freed object from a previous method call.
def Test_freed_object_from_previous_method_call()
  var lines =<< trim END
    vim9script

    class Context
    endclass

    class Result
    endclass

    def Failure(): Result
      return Result.new()
    enddef

    def GetResult(ctx: Context): Result
      return Failure()
    enddef

    def Test_GetResult()
      var ctx = Context.new()
      var result = GetResult(ctx)
    enddef

    Test_GetResult()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for duplicate object and class variable
def Test_duplicate_variable()
  # Object variable name is same as the class variable name
  var lines =<< trim END
    vim9script
    class A
      public static var sval: number
      public var sval: number
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: sval', 4)

  # Duplicate variable name and calling a class method
  lines =<< trim END
    vim9script
    class A
      public static var sval: number
      public var sval: number
      def F1()
        echo this.sval
      enddef
      static def F2()
        echo sval
      enddef
    endclass
    A.F2()
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: sval', 4)

  # Duplicate variable with an empty constructor
  lines =<< trim END
    vim9script
    class A
      public static var sval: number
      public var sval: number
      def new()
      enddef
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1369: Duplicate variable: sval', 4)
enddef

" Test for using a reserved keyword as a variable name
def Test_reserved_varname()
  for kword in ['true', 'false', 'null', 'null_blob', 'null_dict',
                'null_function', 'null_list', 'null_partial', 'null_string',
                'null_channel', 'null_job', 'super', 'this']

    var lines =<< trim eval END
      vim9script
      class C
        public var {kword}: list<number> = [1, 2, 3]
      endclass
      var o = C.new()
    END
    v9.CheckSourceFailure(lines, $'E1034: Cannot use reserved name {kword}', 3)

    lines =<< trim eval END
      vim9script
      class C
        public var {kword}: list<number> = [1, 2, 3]
        def new()
        enddef
      endclass
      var o = C.new()
    END
    v9.CheckSourceFailure(lines, $'E1034: Cannot use reserved name {kword}', 3)

    lines =<< trim eval END
      vim9script
      class C
        public var {kword}: list<number> = [1, 2, 3]
        def new()
        enddef
        def F()
          echo this.{kword}
        enddef
      endclass
      var o = C.new()
      o.F()
    END
    v9.CheckSourceFailure(lines, $'E1034: Cannot use reserved name {kword}', 3)

    # class variable name
    if kword != 'this'
      lines =<< trim eval END
        vim9script
        class C
          public static var {kword}: list<number> = [1, 2, 3]
        endclass
      END
      v9.CheckSourceFailure(lines, $'E1034: Cannot use reserved name {kword}', 3)
    endif
  endfor
enddef

" Test for checking the type of the arguments and the return value of a object
" method in an extended class.
def Test_extended_obj_method_type_check()
  var lines =<< trim END
    vim9script

    class A
    endclass
    class B extends A
    endclass
    class C extends B
    endclass

    class Foo
      def Doit(p: B): B
        return B.new()
      enddef
    endclass

    class Bar extends Foo
      def Doit(p: C): B
        return B.new()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1383: Method "Doit": type mismatch, expected func(object<B>): object<B> but got func(object<C>): object<B>', 20)

  lines =<< trim END
    vim9script

    class A
    endclass
    class B extends A
    endclass
    class C extends B
    endclass

    class Foo
      def Doit(p: B): B
        return B.new()
      enddef
    endclass

    class Bar extends Foo
      def Doit(p: B): C
        return C.new()
      enddef
    endclass
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class A
    endclass
    class B extends A
    endclass
    class C extends B
    endclass

    class Foo
      def Doit(p: B): B
        return B.new()
      enddef
    endclass

    class Bar extends Foo
      def Doit(p: A): B
        return B.new()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1383: Method "Doit": type mismatch, expected func(object<B>): object<B> but got func(object<A>): object<B>', 20)

  lines =<< trim END
    vim9script

    class A
    endclass
    class B extends A
    endclass
    class C extends B
    endclass

    class Foo
      def Doit(p: B): B
        return B.new()
      enddef
    endclass

    class Bar extends Foo
      def Doit(p: B): A
        return A.new()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1383: Method "Doit": type mismatch, expected func(object<B>): object<B> but got func(object<B>): object<A>', 20)

  # check varargs type mismatch
  lines =<< trim END
    vim9script

    class B
      def F(...xxx: list<any>)
      enddef
    endclass
    class C extends B
      def F(xxx: list<any>)
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1383: Method "F": type mismatch, expected func(...list<any>) but got func(list<any>)', 10)
enddef

" Test type checking for class variable in assignments
func Test_class_variable_complex_type_check()
  " class variable with a specific type.  Try assigning a different type at
  " script level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public static var Fn: func(list<dict<blob>>): dict<list<blob>> = Foo
    endclass
    test_garbagecollect_now()
    A.Fn = "abc"
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 9)

  " class variable with a specific type.  Try assigning a different type at
  " class def method level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public static var Fn: func(list<dict<blob>>): dict<list<blob>> = Foo
      def Bar()
        Fn = "abc"
      enddef
    endclass
    var a = A.new()
    test_garbagecollect_now()
    a.Bar()
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 1)

  " class variable with a specific type.  Try assigning a different type at
  " script def method level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public static var Fn: func(list<dict<blob>>): dict<list<blob>> = Foo
    endclass
    def Bar()
      A.Fn = "abc"
    enddef
    test_garbagecollect_now()
    Bar()
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 1)

  " class variable without any type.  Should be set to the initialization
  " expression type.  Try assigning a different type from script level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public static var Fn = Foo
    endclass
    test_garbagecollect_now()
    A.Fn = "abc"
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 9)

  " class variable without any type.  Should be set to the initialization
  " expression type.  Try assigning a different type at class def level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public static var Fn = Foo
      def Bar()
        Fn = "abc"
      enddef
    endclass
    var a = A.new()
    test_garbagecollect_now()
    a.Bar()
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 1)

  " class variable without any type.  Should be set to the initialization
  " expression type.  Try assigning a different type at script def level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public static var Fn = Foo
    endclass
    def Bar()
      A.Fn = "abc"
    enddef
    test_garbagecollect_now()
    Bar()
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 1)

  " class variable with 'any" type.  Can be assigned different types.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public static var Fn: any = Foo
      public static var Fn2: any
    endclass
    test_garbagecollect_now()
    assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(A.Fn))
    A.Fn = "abc"
    test_garbagecollect_now()
    assert_equal('string', typename(A.Fn))
    A.Fn2 = Foo
    test_garbagecollect_now()
    assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(A.Fn2))
    A.Fn2 = "xyz"
    test_garbagecollect_now()
    assert_equal('string', typename(A.Fn2))
  END
  call v9.CheckSourceSuccess(lines)

  " class variable with 'any" type.  Can be assigned different types.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public static var Fn: any = Foo
      public static var Fn2: any

      def Bar()
        assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(Fn))
        Fn = "abc"
        assert_equal('string', typename(Fn))
        Fn2 = Foo
        assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(Fn2))
        Fn2 = "xyz"
        assert_equal('string', typename(Fn2))
      enddef
    endclass
    var a = A.new()
    test_garbagecollect_now()
    a.Bar()
    test_garbagecollect_now()
    A.Fn = Foo
    a.Bar()
  END
  call v9.CheckSourceSuccess(lines)

  " class variable with 'any" type.  Can be assigned different types.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public static var Fn: any = Foo
      public static var Fn2: any
    endclass

    def Bar()
      assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(A.Fn))
      A.Fn = "abc"
      assert_equal('string', typename(A.Fn))
      A.Fn2 = Foo
      assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(A.Fn2))
      A.Fn2 = "xyz"
      assert_equal('string', typename(A.Fn2))
    enddef
    Bar()
    test_garbagecollect_now()
    A.Fn = Foo
    Bar()
  END
  call v9.CheckSourceSuccess(lines)

  let lines =<< trim END
    vim9script
    class A
      public static var foo = [0z10, 0z20]
    endclass
    assert_equal([0z10, 0z20], A.foo)
    A.foo = [0z30]
    assert_equal([0z30], A.foo)
    var a = A.foo
    assert_equal([0z30], a)
  END
  call v9.CheckSourceSuccess(lines)
endfunc

" Test type checking for object variable in assignments
func Test_object_variable_complex_type_check()
  " object variable with a specific type.  Try assigning a different type at
  " script level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public var Fn: func(list<dict<blob>>): dict<list<blob>> = Foo
    endclass
    var a = A.new()
    test_garbagecollect_now()
    a.Fn = "abc"
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 10)

  " object variable with a specific type.  Try assigning a different type at
  " object def method level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public var Fn: func(list<dict<blob>>): dict<list<blob>> = Foo
      def Bar()
        this.Fn = "abc"
        this.Fn = Foo
      enddef
    endclass
    var a = A.new()
    test_garbagecollect_now()
    a.Bar()
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 1)

  " object variable with a specific type.  Try assigning a different type at
  " script def method level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public var Fn: func(list<dict<blob>>): dict<list<blob>> = Foo
    endclass
    def Bar()
      var a = A.new()
      a.Fn = "abc"
      a.Fn = Foo
    enddef
    test_garbagecollect_now()
    Bar()
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 2)

  " object variable without any type.  Should be set to the initialization
  " expression type.  Try assigning a different type from script level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public var Fn = Foo
    endclass
    var a = A.new()
    test_garbagecollect_now()
    a.Fn = "abc"
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 10)

  " object variable without any type.  Should be set to the initialization
  " expression type.  Try assigning a different type at object def level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public var Fn = Foo
      def Bar()
        this.Fn = "abc"
        this.Fn = Foo
      enddef
    endclass
    var a = A.new()
    test_garbagecollect_now()
    a.Bar()
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 1)

  " object variable without any type.  Should be set to the initialization
  " expression type.  Try assigning a different type at script def level.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public var Fn = Foo
    endclass
    def Bar()
      var a = A.new()
      a.Fn = "abc"
      a.Fn = Foo
    enddef
    test_garbagecollect_now()
    Bar()
  END
  call v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(list<dict<blob>>): dict<list<blob>> but got string', 2)

  " object variable with 'any" type.  Can be assigned different types.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public var Fn: any = Foo
      public var Fn2: any
    endclass

    var a = A.new()
    test_garbagecollect_now()
    assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(a.Fn))
    a.Fn = "abc"
    test_garbagecollect_now()
    assert_equal('string', typename(a.Fn))
    a.Fn2 = Foo
    test_garbagecollect_now()
    assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(a.Fn2))
    a.Fn2 = "xyz"
    test_garbagecollect_now()
    assert_equal('string', typename(a.Fn2))
  END
  call v9.CheckSourceSuccess(lines)

  " object variable with 'any" type.  Can be assigned different types.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public var Fn: any = Foo
      public var Fn2: any

      def Bar()
        assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(this.Fn))
        this.Fn = "abc"
        assert_equal('string', typename(this.Fn))
        this.Fn2 = Foo
        assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(this.Fn2))
        this.Fn2 = "xyz"
        assert_equal('string', typename(this.Fn2))
      enddef
    endclass

    var a = A.new()
    test_garbagecollect_now()
    a.Bar()
    test_garbagecollect_now()
    a.Fn = Foo
    a.Bar()
  END
  call v9.CheckSourceSuccess(lines)

  " object variable with 'any" type.  Can be assigned different types.
  let lines =<< trim END
    vim9script
    def Foo(l: list<dict<blob>>): dict<list<blob>>
      return {}
    enddef
    class A
      public var Fn: any = Foo
      public var Fn2: any
    endclass

    def Bar()
      var a = A.new()
      assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(a.Fn))
      a.Fn = "abc"
      assert_equal('string', typename(a.Fn))
      a.Fn2 = Foo
      assert_equal('func(list<dict<blob>>): dict<list<blob>>', typename(a.Fn2))
      a.Fn2 = "xyz"
      assert_equal('string', typename(a.Fn2))
    enddef
    test_garbagecollect_now()
    Bar()
    test_garbagecollect_now()
    Bar()
  END
  call v9.CheckSourceSuccess(lines)
endfunc

" Test for recursively calling an object method.  This used to cause an
" use-after-free error.
def Test_recursive_object_method_call()
  var lines =<< trim END
    vim9script
    class A
      var val: number = 0
      def Foo(): number
        if this.val >= 90
          return this.val
        endif
        this.val += 1
        return this.Foo()
      enddef
    endclass
    var a = A.new()
    assert_equal(90, a.Foo())
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for recursively calling a class method.
def Test_recursive_class_method_call()
  var lines =<< trim END
    vim9script
    class A
      static var val: number = 0
      static def Foo(): number
        if val >= 90
          return val
        endif
        val += 1
        return Foo()
      enddef
    endclass
    assert_equal(90, A.Foo())
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for checking the argument types and the return type when assigning a
" funcref to make sure the invariant class type is used.
def Test_funcref_argtype_returntype_check()
  var lines =<< trim END
    vim9script
    class A
    endclass
    class B extends A
    endclass

    def Foo(p: B): B
      return B.new()
    enddef

    var Bar: func(A): A = Foo
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(object<A>): object<A> but got func(object<B>): object<B>', 11)

  lines =<< trim END
    vim9script
    class A
    endclass
    class B extends A
    endclass

    def Foo(p: B): B
      return B.new()
    enddef

    def Baz()
      var Bar: func(A): A = Foo
    enddef
    Baz()
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(object<A>): object<A> but got func(object<B>): object<B>', 1)
enddef

def Test_funcref_argtype_invariance_check()
  var lines =<< trim END
    vim9script

    class A
    endclass
    class B extends A
    endclass
    class C extends B
    endclass

    var Func: func(B): number
    Func = (o: B): number => 3
    assert_equal(3, Func(B.new()))
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class A
    endclass
    class B extends A
    endclass
    class C extends B
    endclass

    var Func: func(B): number
    Func = (o: A): number => 3
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(object<B>): number but got func(object<A>): number', 11)

  lines =<< trim END
    vim9script

    class A
    endclass
    class B extends A
    endclass
    class C extends B
    endclass

    var Func: func(B): number
    Func = (o: C): number => 3
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected func(object<B>): number but got func(object<C>): number', 11)
enddef

" Test for using an operator (e.g. +) with an assignment
def Test_op_and_assignment()
  # Using += with a class variable
  var lines =<< trim END
    vim9script
    class A
      public static var val: list<number> = []
      static def Foo(): list<number>
        val += [1]
        return val
      enddef
    endclass
    def Bar(): list<number>
      A.val += [2]
      return A.val
    enddef
    assert_equal([1], A.Foo())
    assert_equal([1, 2], Bar())
    A.val += [3]
    assert_equal([1, 2, 3], A.val)
  END
  v9.CheckSourceSuccess(lines)

  # Using += with an object variable
  lines =<< trim END
    vim9script
    class A
      public var val: list<number> = []
      def Foo(): list<number>
        this.val += [1]
        return this.val
      enddef
    endclass
    def Bar(bar_a: A): list<number>
      bar_a.val += [2]
      return bar_a.val
    enddef
    var a = A.new()
    assert_equal([1], a.Foo())
    assert_equal([1, 2], Bar(a))
    a.val += [3]
    assert_equal([1, 2, 3], a.val)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using an object method as a funcref
def Test_object_funcref()
  # Using object method funcref from a def function
  var lines =<< trim END
    vim9script
    class A
      def Foo(): list<number>
        return [3, 2, 1]
      enddef
    endclass
    def Bar()
      var a = A.new()
      var Fn = a.Foo
      assert_equal([3, 2, 1], Fn())
    enddef
    Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using object method funcref at the script level
  lines =<< trim END
    vim9script
    class A
      def Foo(): dict<number>
        return {a: 1, b: 2}
      enddef
    endclass
    var a = A.new()
    var Fn = a.Foo
    assert_equal({a: 1, b: 2}, Fn())
  END
  v9.CheckSourceSuccess(lines)

  # Using object method funcref at the script level
  lines =<< trim END
    vim9script
    class A
      var val: number
      def Foo(): number
        return this.val
      enddef
    endclass
    var a = A.new(345)
    var Fn = a.Foo
    assert_equal(345, Fn())
  END
  v9.CheckSourceSuccess(lines)

  # Using object method funcref from another object method
  lines =<< trim END
    vim9script
    class A
      def Foo(): list<number>
        return [3, 2, 1]
      enddef
      def Bar()
        var Fn = this.Foo
        assert_equal([3, 2, 1], Fn())
      enddef
    endclass
    var a = A.new()
    a.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using function() to get a object method funcref
  lines =<< trim END
    vim9script
    class A
      def Foo(l: list<any>): list<any>
        return l
      enddef
    endclass
    var a = A.new()
    var Fn = function(a.Foo, [[{a: 1, b: 2}, [3, 4]]])
    assert_equal([{a: 1, b: 2}, [3, 4]], Fn())
  END
  v9.CheckSourceSuccess(lines)

  # Use an object method with a function returning a funcref and then call the
  # funcref.
  lines =<< trim END
    vim9script

    def Map(F: func(number): number): func(number): number
      return (n: number) => F(n)
    enddef

    class Math
      def Double(n: number): number
        return 2 * n
      enddef
    endclass

    const math = Math.new()
    assert_equal(48, Map(math.Double)(24))
  END
  v9.CheckSourceSuccess(lines)

  # Try using a protected object method funcref from a def function
  lines =<< trim END
    vim9script
    class A
      def _Foo()
      enddef
    endclass
    def Bar()
      var a = A.new()
      var Fn = a._Foo
    enddef
    Bar()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 2)

  # Try using a protected object method funcref at the script level
  lines =<< trim END
    vim9script
    class A
      def _Foo()
      enddef
    endclass
    var a = A.new()
    var Fn = a._Foo
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 7)

  # Using a protected object method funcref from another object method
  lines =<< trim END
    vim9script
    class A
      def _Foo(): list<number>
        return [3, 2, 1]
      enddef
      def Bar()
        var Fn = this._Foo
        assert_equal([3, 2, 1], Fn())
      enddef
    endclass
    var a = A.new()
    a.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using object method funcref using call()
  lines =<< trim END
    vim9script
    class A
      var val: number
      def Foo(): number
        return this.val
      enddef
    endclass

    def Bar(obj: A)
      assert_equal(123, call(obj.Foo, []))
    enddef

    var a = A.new(123)
    Bar(a)
    assert_equal(123, call(a.Foo, []))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using a class method as a funcref
def Test_class_funcref()
  # Using class method funcref in a def function
  var lines =<< trim END
    vim9script
    class A
      static def Foo(): list<number>
        return [3, 2, 1]
      enddef
    endclass
    def Bar()
      var Fn = A.Foo
      assert_equal([3, 2, 1], Fn())
    enddef
    Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using class method funcref at script level
  lines =<< trim END
    vim9script
    class A
      static def Foo(): dict<number>
        return {a: 1, b: 2}
      enddef
    endclass
    var Fn = A.Foo
    assert_equal({a: 1, b: 2}, Fn())
  END
  v9.CheckSourceSuccess(lines)

  # Using class method funcref at the script level
  lines =<< trim END
    vim9script
    class A
      public static var val: number
      static def Foo(): number
        return val
      enddef
    endclass
    A.val = 567
    var Fn = A.Foo
    assert_equal(567, Fn())
  END
  v9.CheckSourceSuccess(lines)

  # Using function() to get a class method funcref
  lines =<< trim END
    vim9script
    class A
      static def Foo(l: list<any>): list<any>
        return l
      enddef
    endclass
    var Fn = function(A.Foo, [[{a: 1, b: 2}, [3, 4]]])
    assert_equal([{a: 1, b: 2}, [3, 4]], Fn())
  END
  v9.CheckSourceSuccess(lines)

  # Using a class method funcref from another class method
  lines =<< trim END
    vim9script
    class A
      static def Foo(): list<number>
        return [3, 2, 1]
      enddef
      static def Bar()
        var Fn = Foo
        assert_equal([3, 2, 1], Fn())
      enddef
    endclass
    A.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Use a class method with a function returning a funcref and then call the
  # funcref.
  lines =<< trim END
    vim9script

    def Map(F: func(number): number): func(number): number
      return (n: number) => F(n)
    enddef

    class Math
      static def StaticDouble(n: number): number
        return 2 * n
      enddef
    endclass

    assert_equal(48, Map(Math.StaticDouble)(24))
  END
  v9.CheckSourceSuccess(lines)

  # Try using a protected class method funcref in a def function
  lines =<< trim END
    vim9script
    class A
      static def _Foo()
      enddef
    endclass
    def Bar()
      var Fn = A._Foo
    enddef
    Bar()
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 1)

  # Try using a protected class method funcref at script level
  lines =<< trim END
    vim9script
    class A
      static def _Foo()
      enddef
    endclass
    var Fn = A._Foo
  END
  v9.CheckSourceFailure(lines, 'E1366: Cannot access protected method: _Foo', 6)

  # Using a protected class method funcref from another class method
  lines =<< trim END
    vim9script
    class A
      static def _Foo(): list<number>
        return [3, 2, 1]
      enddef
      static def Bar()
        var Fn = _Foo
        assert_equal([3, 2, 1], Fn())
      enddef
    endclass
    A.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using class method funcref using call()
  lines =<< trim END
    vim9script
    class A
      public static var val: number
      static def Foo(): number
        return val
      enddef
    endclass

    def Bar()
      A.val = 468
      assert_equal(468, call(A.Foo, []))
    enddef
    Bar()
    assert_equal(468, call(A.Foo, []))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using an object member as a funcref
def Test_object_member_funcref()
  # Using a funcref object variable in an object method
  var lines =<< trim END
    vim9script
    def Foo(n: number): number
      return n * 10
    enddef

    class A
      var Cb: func(number): number = Foo
      def Bar()
        assert_equal(200, this.Cb(20))
      enddef
    endclass

    var a = A.new()
    a.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref object variable in a def method
  lines =<< trim END
    vim9script
    def Foo(n: number): number
      return n * 10
    enddef

    class A
      var Cb: func(number): number = Foo
    endclass

    def Bar()
      var a = A.new()
      assert_equal(200, a.Cb(20))
    enddef
    Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref object variable at script level
  lines =<< trim END
    vim9script
    def Foo(n: number): number
      return n * 10
    enddef

    class A
      var Cb: func(number): number = Foo
    endclass

    var a = A.new()
    assert_equal(200, a.Cb(20))
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref object variable pointing to an object method in an object
  # method.
  lines =<< trim END
    vim9script
    class A
      var Cb: func(number): number = this.Foo
      def Foo(n: number): number
        return n * 10
      enddef
      def Bar()
        assert_equal(200, this.Cb(20))
      enddef
    endclass

    var a = A.new()
    a.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref object variable pointing to an object method in a def
  # method.
  lines =<< trim END
    vim9script
    class A
      var Cb: func(number): number = this.Foo
      def Foo(n: number): number
        return n * 10
      enddef
    endclass

    def Bar()
      var a = A.new()
      assert_equal(200, a.Cb(20))
    enddef
    Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref object variable pointing to an object method at script
  # level.
  lines =<< trim END
    vim9script
    class A
      var Cb = this.Foo
      def Foo(n: number): number
        return n * 10
      enddef
    endclass

    var a = A.new()
    assert_equal(200, a.Cb(20))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using a class member as a funcref
def Test_class_member_funcref()
  # Using a funcref class variable in a class method
  var lines =<< trim END
    vim9script
    def Foo(n: number): number
      return n * 10
    enddef

    class A
      static var Cb = Foo
      static def Bar()
        assert_equal(200, Cb(20))
      enddef
    endclass

    A.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref class variable in a def method
  lines =<< trim END
    vim9script
    def Foo(n: number): number
      return n * 10
    enddef

    class A
      public static var Cb = Foo
    endclass

    def Bar()
      assert_equal(200, A.Cb(20))
    enddef
    Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref class variable at script level
  lines =<< trim END
    vim9script
    def Foo(n: number): number
      return n * 10
    enddef

    class A
      public static var Cb = Foo
    endclass

    assert_equal(200, A.Cb(20))
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref class variable pointing to a class method in a class
  # method.
  lines =<< trim END
    vim9script
    class A
      static var Cb: func(number): number
      static def Foo(n: number): number
        return n * 10
      enddef
      static def Init()
        Cb = Foo
      enddef
      static def Bar()
        assert_equal(200, Cb(20))
      enddef
    endclass

    A.Init()
    A.Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref class variable pointing to a class method in a def method.
  lines =<< trim END
    vim9script
    class A
      static var Cb: func(number): number
      static def Foo(n: number): number
        return n * 10
      enddef
      static def Init()
        Cb = Foo
      enddef
    endclass

    def Bar()
      A.Init()
      assert_equal(200, A.Cb(20))
    enddef
    Bar()
  END
  v9.CheckSourceSuccess(lines)

  # Using a funcref class variable pointing to a class method at script level.
  lines =<< trim END
    vim9script
    class A
      static var Cb: func(number): number
      static def Foo(n: number): number
        return n * 10
      enddef
      static def Init()
        Cb = Foo
      enddef
    endclass

    A.Init()
    assert_equal(200, A.Cb(20))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using object methods as popup callback functions
def Test_objmethod_popup_callback()
  # Use the popup from the script level
  var lines =<< trim END
    vim9script

    class A
      var selection: number = -1
      var filterkeys: list<string> = []

      def PopupFilter(id: number, key: string): bool
        add(this.filterkeys, key)
        return popup_filter_yesno(id, key)
      enddef

      def PopupCb(id: number, result: number)
        this.selection = result ? 100 : 200
      enddef
    endclass

    var a = A.new()
    feedkeys('', 'xt')
    var winid = popup_create('Y/N?',
                              {filter: a.PopupFilter, callback: a.PopupCb})
    feedkeys('y', 'xt')
    popup_close(winid)
    assert_equal(100, a.selection)
    assert_equal(['y'], a.filterkeys)
    feedkeys('', 'xt')
    winid = popup_create('Y/N?',
                              {filter: a.PopupFilter, callback: a.PopupCb})
    feedkeys('n', 'xt')
    popup_close(winid)
    assert_equal(200, a.selection)
    assert_equal(['y', 'n'], a.filterkeys)
  END
  v9.CheckSourceSuccess(lines)

  # Use the popup from a def function
  lines =<< trim END
    vim9script

    class A
      var selection: number = -1
      var filterkeys: list<string> = []

      def PopupFilter(id: number, key: string): bool
        add(this.filterkeys, key)
        return popup_filter_yesno(id, key)
      enddef

      def PopupCb(id: number, result: number)
        this.selection = result ? 100 : 200
      enddef
    endclass

    def Foo()
      var a = A.new()
      feedkeys('', 'xt')
      var winid = popup_create('Y/N?',
                                {filter: a.PopupFilter, callback: a.PopupCb})
      feedkeys('y', 'xt')
      popup_close(winid)
      assert_equal(100, a.selection)
      assert_equal(['y'], a.filterkeys)
      feedkeys('', 'xt')
      winid = popup_create('Y/N?',
                                {filter: a.PopupFilter, callback: a.PopupCb})
      feedkeys('n', 'xt')
      popup_close(winid)
      assert_equal(200, a.selection)
      assert_equal(['y', 'n'], a.filterkeys)
    enddef
    Foo()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using class methods as popup callback functions
def Test_classmethod_popup_callback()
  # Use the popup from the script level
  var lines =<< trim END
    vim9script

    class A
      static var selection: number = -1
      static var filterkeys: list<string> = []

      static def PopupFilter(id: number, key: string): bool
        add(filterkeys, key)
        return popup_filter_yesno(id, key)
      enddef

      static def PopupCb(id: number, result: number)
        selection = result ? 100 : 200
      enddef
    endclass

    feedkeys('', 'xt')
    var winid = popup_create('Y/N?',
                              {filter: A.PopupFilter, callback: A.PopupCb})
    feedkeys('y', 'xt')
    popup_close(winid)
    assert_equal(100, A.selection)
    assert_equal(['y'], A.filterkeys)
    feedkeys('', 'xt')
    winid = popup_create('Y/N?',
                              {filter: A.PopupFilter, callback: A.PopupCb})
    feedkeys('n', 'xt')
    popup_close(winid)
    assert_equal(200, A.selection)
    assert_equal(['y', 'n'], A.filterkeys)
  END
  v9.CheckSourceSuccess(lines)

  # Use the popup from a def function
  lines =<< trim END
    vim9script

    class A
      static var selection: number = -1
      static var filterkeys: list<string> = []

      static def PopupFilter(id: number, key: string): bool
        add(filterkeys, key)
        return popup_filter_yesno(id, key)
      enddef

      static def PopupCb(id: number, result: number)
        selection = result ? 100 : 200
      enddef
    endclass

    def Foo()
      feedkeys('', 'xt')
      var winid = popup_create('Y/N?',
                                {filter: A.PopupFilter, callback: A.PopupCb})
      feedkeys('y', 'xt')
      popup_close(winid)
      assert_equal(100, A.selection)
      assert_equal(['y'], A.filterkeys)
      feedkeys('', 'xt')
      winid = popup_create('Y/N?',
                                {filter: A.PopupFilter, callback: A.PopupCb})
      feedkeys('n', 'xt')
      popup_close(winid)
      assert_equal(200, A.selection)
      assert_equal(['y', 'n'], A.filterkeys)
    enddef
    Foo()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using an object method as a timer callback function
def Test_objmethod_timer_callback()
  # Use the timer callback from script level
  var lines =<< trim END
    vim9script

    class A
      var timerTick: number = -1
      def TimerCb(timerID: number)
        this.timerTick = 6
      enddef
    endclass

    var a = A.new()
    timer_start(0, a.TimerCb)
    var maxWait = 5
    while maxWait > 0 && a.timerTick == -1
      :sleep 10m
      maxWait -= 1
    endwhile
    assert_equal(6, a.timerTick)
  END
  v9.CheckSourceSuccess(lines)

  # Use the timer callback from a def function
  lines =<< trim END
    vim9script

    class A
      var timerTick: number = -1
      def TimerCb(timerID: number)
        this.timerTick = 6
      enddef
    endclass

    def Foo()
      var a = A.new()
      timer_start(0, a.TimerCb)
      var maxWait = 5
      while maxWait > 0 && a.timerTick == -1
        :sleep 10m
        maxWait -= 1
      endwhile
      assert_equal(6, a.timerTick)
    enddef
    Foo()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using a class method as a timer callback function
def Test_classmethod_timer_callback()
  # Use the timer callback from script level
  var lines =<< trim END
    vim9script

    class A
      static var timerTick: number = -1
      static def TimerCb(timerID: number)
        timerTick = 6
      enddef
    endclass

    timer_start(0, A.TimerCb)
    var maxWait = 5
    while maxWait > 0 && A.timerTick == -1
      :sleep 10m
      maxWait -= 1
    endwhile
    assert_equal(6, A.timerTick)
  END
  v9.CheckSourceSuccess(lines)

  # Use the timer callback from a def function
  lines =<< trim END
    vim9script

    class A
      static var timerTick: number = -1
      static def TimerCb(timerID: number)
        timerTick = 6
      enddef
    endclass

    def Foo()
      timer_start(0, A.TimerCb)
      var maxWait = 5
      while maxWait > 0 && A.timerTick == -1
        :sleep 10m
        maxWait -= 1
      endwhile
      assert_equal(6, A.timerTick)
    enddef
    Foo()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using a class variable as the first and/or second operand of a binary
" operator.
def Test_class_variable_as_operands()
  var lines =<< trim END
    vim9script
    class Tests
      static var truthy: bool = true
      public static var TruthyFn: func
      static var list: list<any> = []
      static var four: number = 4
      static var str: string = 'hello'

      static def Str(): string
        return str
      enddef

      static def Four(): number
        return four
      enddef

      static def List(): list<any>
        return list
      enddef

      static def Truthy(): bool
        return truthy
      enddef

      def TestOps()
        assert_true(Tests.truthy == truthy)
        assert_true(truthy == Tests.truthy)
        assert_true(Tests.list isnot [])
        assert_true([] isnot Tests.list)
        assert_equal(2, Tests.four >> 1)
        assert_equal(16, 1 << Tests.four)
        assert_equal(8, Tests.four + four)
        assert_equal(8, four + Tests.four)
        assert_equal('hellohello', Tests.str .. str)
        assert_equal('hellohello', str .. Tests.str)

        # Using class variable for list indexing
        var l = range(10)
        assert_equal(4, l[Tests.four])
        assert_equal([4, 5, 6], l[Tests.four : Tests.four + 2])

        # Using class variable for Dict key
        var d = {hello: 'abc'}
        assert_equal('abc', d[Tests.str])
      enddef
    endclass

    def TestOps2()
      assert_true(Tests.truthy == Tests.Truthy())
      assert_true(Tests.Truthy() == Tests.truthy)
      assert_true(Tests.truthy == Tests.TruthyFn())
      assert_true(Tests.TruthyFn() == Tests.truthy)
      assert_true(Tests.list is Tests.List())
      assert_true(Tests.List() is Tests.list)
      assert_equal(2, Tests.four >> 1)
      assert_equal(16, 1 << Tests.four)
      assert_equal(8, Tests.four + Tests.Four())
      assert_equal(8, Tests.Four() + Tests.four)
      assert_equal('hellohello', Tests.str .. Tests.Str())
      assert_equal('hellohello', Tests.Str() .. Tests.str)

      # Using class variable for list indexing
      var l = range(10)
      assert_equal(4, l[Tests.four])
      assert_equal([4, 5, 6], l[Tests.four : Tests.four + 2])

      # Using class variable for Dict key
      var d = {hello: 'abc'}
      assert_equal('abc', d[Tests.str])
    enddef

    Tests.TruthyFn = Tests.Truthy
    var t = Tests.new()
    t.TestOps()
    TestOps2()

    assert_true(Tests.truthy == Tests.Truthy())
    assert_true(Tests.Truthy() == Tests.truthy)
    assert_true(Tests.truthy == Tests.TruthyFn())
    assert_true(Tests.TruthyFn() == Tests.truthy)
    assert_true(Tests.list is Tests.List())
    assert_true(Tests.List() is Tests.list)
    assert_equal(2, Tests.four >> 1)
    assert_equal(16, 1 << Tests.four)
    assert_equal(8, Tests.four + Tests.Four())
    assert_equal(8, Tests.Four() + Tests.four)
    assert_equal('hellohello', Tests.str .. Tests.Str())
    assert_equal('hellohello', Tests.Str() .. Tests.str)

    # Using class variable for list indexing
    var l = range(10)
    assert_equal(4, l[Tests.four])
    assert_equal([4, 5, 6], l[Tests.four : Tests.four + 2])

    # Using class variable for Dict key
    var d = {hello: 'abc'}
    assert_equal('abc', d[Tests.str])
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for checking the type of the key used to access an object dict member.
def Test_dict_member_key_type_check()
  var lines =<< trim END
    vim9script

    abstract class State
      var numbers: dict<string> = {0: 'nil', 1: 'unity'}
    endclass

    class Test extends State
      def ObjMethodTests()
        var cursor: number = 0
        var z: number = 0
        [this.numbers[cursor]] = ['zero.1']
        assert_equal({0: 'zero.1', 1: 'unity'}, this.numbers)
        [this.numbers[string(cursor)], z] = ['zero.2', 1]
        assert_equal({0: 'zero.2', 1: 'unity'}, this.numbers)
        [z, this.numbers[string(cursor)]] = [1, 'zero.3']
        assert_equal({0: 'zero.3', 1: 'unity'}, this.numbers)
        [this.numbers[cursor], z] = ['zero.4', 1]
        assert_equal({0: 'zero.4', 1: 'unity'}, this.numbers)
        [z, this.numbers[cursor]] = [1, 'zero.5']
        assert_equal({0: 'zero.5', 1: 'unity'}, this.numbers)
      enddef

      static def ClassMethodTests(that: State)
        var cursor: number = 0
        var z: number = 0
        [that.numbers[cursor]] = ['zero.1']
        assert_equal({0: 'zero.1', 1: 'unity'}, that.numbers)
        [that.numbers[string(cursor)], z] = ['zero.2', 1]
        assert_equal({0: 'zero.2', 1: 'unity'}, that.numbers)
        [z, that.numbers[string(cursor)]] = [1, 'zero.3']
        assert_equal({0: 'zero.3', 1: 'unity'}, that.numbers)
        [that.numbers[cursor], z] = ['zero.4', 1]
        assert_equal({0: 'zero.4', 1: 'unity'}, that.numbers)
        [z, that.numbers[cursor]] = [1, 'zero.5']
        assert_equal({0: 'zero.5', 1: 'unity'}, that.numbers)
      enddef

      def new()
      enddef

      def newMethodTests()
        var cursor: number = 0
        var z: number
        [this.numbers[cursor]] = ['zero.1']
        assert_equal({0: 'zero.1', 1: 'unity'}, this.numbers)
        [this.numbers[string(cursor)], z] = ['zero.2', 1]
        assert_equal({0: 'zero.2', 1: 'unity'}, this.numbers)
        [z, this.numbers[string(cursor)]] = [1, 'zero.3']
        assert_equal({0: 'zero.3', 1: 'unity'}, this.numbers)
        [this.numbers[cursor], z] = ['zero.4', 1]
        assert_equal({0: 'zero.4', 1: 'unity'}, this.numbers)
        [z, this.numbers[cursor]] = [1, 'zero.5']
        assert_equal({0: 'zero.5', 1: 'unity'}, this.numbers)
      enddef
    endclass

    def DefFuncTests(that: Test)
      var cursor: number = 0
      var z: number
      [that.numbers[cursor]] = ['zero.1']
      assert_equal({0: 'zero.1', 1: 'unity'}, that.numbers)
      [that.numbers[string(cursor)], z] = ['zero.2', 1]
      assert_equal({0: 'zero.2', 1: 'unity'}, that.numbers)
      [z, that.numbers[string(cursor)]] = [1, 'zero.3']
      assert_equal({0: 'zero.3', 1: 'unity'}, that.numbers)
      [that.numbers[cursor], z] = ['zero.4', 1]
      assert_equal({0: 'zero.4', 1: 'unity'}, that.numbers)
      [z, that.numbers[cursor]] = [1, 'zero.5']
      assert_equal({0: 'zero.5', 1: 'unity'}, that.numbers)
    enddef

    Test.newMethodTests()
    Test.new().ObjMethodTests()
    Test.ClassMethodTests(Test.new())
    DefFuncTests(Test.new())

    const test: Test = Test.new()
    var cursor: number = 0
    [test.numbers[cursor], cursor] = ['zero', 1]
    [cursor, test.numbers[cursor]] = [1, 'one']
    assert_equal({0: 'zero', 1: 'one'}, test.numbers)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class A
      var numbers: dict<string> = {a: '1', b: '2'}

      def new()
      enddef

      def Foo()
        var z: number
        [this.numbers.a, z] = [{}, 10]
      enddef
    endclass

    var a = A.new()
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected string but got dict<any>', 2)

  lines =<< trim END
    vim9script

    class A
      var numbers: dict<number> = {a: 1, b: 2}

      def new()
      enddef

      def Foo()
        var x: string = 'a'
        var y: number
        [this.numbers[x], y] = [{}, 10]
      enddef
    endclass

    var a = A.new()
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected number but got dict<any>', 3)
enddef

def Test_compile_many_def_functions_in_funcref_instr()
  # This used to crash Vim.  This is reproducible only when run on new instance
  # of Vim.
  var lines =<< trim END
    vim9script

    class A
      def new()
        this.TakeFunc(this.F00)
      enddef

      def TakeFunc(F: func)
      enddef

      def F00()
        this.F01()
        this.F02()
        this.F03()
        this.F04()
        this.F05()
        this.F06()
        this.F07()
        this.F08()
        this.F09()
        this.F10()
        this.F11()
        this.F12()
        this.F13()
        this.F14()
        this.F15()
        this.F16()
        this.F17()
        this.F18()
        this.F19()
        this.F20()
        this.F21()
        this.F22()
        this.F23()
        this.F24()
        this.F25()
        this.F26()
        this.F27()
        this.F28()
        this.F29()
        this.F30()
        this.F31()
        this.F32()
        this.F33()
        this.F34()
        this.F35()
        this.F36()
        this.F37()
        this.F38()
        this.F39()
        this.F40()
        this.F41()
        this.F42()
        this.F43()
        this.F44()
        this.F45()
        this.F46()
        this.F47()
      enddef

      def F01()
      enddef
      def F02()
      enddef
      def F03()
      enddef
      def F04()
      enddef
      def F05()
      enddef
      def F06()
      enddef
      def F07()
      enddef
      def F08()
      enddef
      def F09()
      enddef
      def F10()
      enddef
      def F11()
      enddef
      def F12()
      enddef
      def F13()
      enddef
      def F14()
      enddef
      def F15()
      enddef
      def F16()
      enddef
      def F17()
      enddef
      def F18()
      enddef
      def F19()
      enddef
      def F20()
      enddef
      def F21()
      enddef
      def F22()
      enddef
      def F23()
      enddef
      def F24()
      enddef
      def F25()
      enddef
      def F26()
      enddef
      def F27()
      enddef
      def F28()
      enddef
      def F29()
      enddef
      def F30()
      enddef
      def F31()
      enddef
      def F32()
      enddef
      def F33()
      enddef
      def F34()
      enddef
      def F35()
      enddef
      def F36()
      enddef
      def F37()
      enddef
      def F38()
      enddef
      def F39()
      enddef
      def F40()
      enddef
      def F41()
      enddef
      def F42()
      enddef
      def F43()
      enddef
      def F44()
      enddef
      def F45()
      enddef
      def F46()
      enddef
      def F47()
      enddef
    endclass

    A.new()
  END
  writefile(lines, 'Xscript', 'D')
  g:RunVim([], [], '-u NONE -S Xscript -c qa')
  assert_equal(0, v:shell_error)
enddef

" Test for 'final' class and object variables
def Test_final_class_object_variable()
  # Test for changing a final object variable from an object function
  var lines =<< trim END
    vim9script
    class A
      final foo: string = "abc"
      def Foo()
        this.foo = "def"
      enddef
    endclass
    defcompile A.Foo
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "foo" in class "A"', 1)

  # Test for changing a final object variable from the 'new' function
  lines =<< trim END
    vim9script
    class A
      final s1: string
      final s2: string
      def new(this.s1)
        this.s2 = 'def'
      enddef
    endclass
    var a = A.new('abc')
    assert_equal('abc', a.s1)
    assert_equal('def', a.s2)
  END
  v9.CheckSourceSuccess(lines)

  # Test for a final class variable
  lines =<< trim END
    vim9script
    class A
      static final s1: string = "abc"
    endclass
    assert_equal('abc', A.s1)
  END
  v9.CheckSourceSuccess(lines)

  # Test for changing a final class variable from a class function
  lines =<< trim END
    vim9script
    class A
      static final s1: string = "abc"
      static def Foo()
        s1 = "def"
      enddef
    endclass
    A.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)

  # Test for changing a public final class variable at script level
  lines =<< trim END
    vim9script
    class A
      public static final s1: string = "abc"
    endclass
    assert_equal('abc', A.s1)
    A.s1 = 'def'
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 6)

  # Test for changing a public final class variable from a class function
  lines =<< trim END
    vim9script
    class A
      public static final s1: string = "abc"
      static def Foo()
        s1 = "def"
      enddef
    endclass
    A.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)

  # Test for changing a public final class variable from a function
  lines =<< trim END
    vim9script
    class A
      public static final s1: string = "abc"
    endclass
    def Foo()
      A.s1 = 'def'
    enddef
    defcompile
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)

  # Test for using a final variable of composite type
  lines =<< trim END
    vim9script
    class A
      public final l: list<number>
      def new()
        this.l = [1, 2]
      enddef
      def Foo()
        this.l[0] = 3
        this.l->add(4)
      enddef
    endclass
    var a = A.new()
    assert_equal([1, 2], a.l)
    a.Foo()
    assert_equal([3, 2, 4], a.l)
  END
  v9.CheckSourceSuccess(lines)

  # Test for changing a final variable of composite type from another object
  # function
  lines =<< trim END
    vim9script
    class A
      public final l: list<number> = [1, 2]
      def Foo()
        this.l = [3, 4]
      enddef
    endclass
    var a = A.new()
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)

  # Test for modifying a final variable of composite type at script level
  lines =<< trim END
    vim9script
    class A
      public final l: list<number> = [1, 2]
    endclass
    var a = A.new()
    a.l[0] = 3
    a.l->add(4)
    assert_equal([3, 2, 4], a.l)
  END
  v9.CheckSourceSuccess(lines)

  # Test for modifying a final variable of composite type from a function
  lines =<< trim END
    vim9script
    class A
      public final l: list<number> = [1, 2]
    endclass
    def Foo()
      var a = A.new()
      a.l[0] = 3
      a.l->add(4)
      assert_equal([3, 2, 4], a.l)
    enddef
    Foo()
  END
  v9.CheckSourceSuccess(lines)

  # Test for modifying a final variable of composite type from another object
  # function
  lines =<< trim END
    vim9script
    class A
      public final l: list<number> = [1, 2]
      def Foo()
        this.l[0] = 3
        this.l->add(4)
      enddef
    endclass
    var a = A.new()
    a.Foo()
    assert_equal([3, 2, 4], a.l)
  END
  v9.CheckSourceSuccess(lines)

  # Test for assigning a new value to a final variable of composite type at
  # script level
  lines =<< trim END
    vim9script
    class A
      public final l: list<number> = [1, 2]
    endclass
    var a = A.new()
    a.l = [3, 4]
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 6)

  # Test for assigning a new value to a final variable of composite type from
  # another object function
  lines =<< trim END
    vim9script
    class A
      public final l: list<number> = [1, 2]
      def Foo()
        this.l = [3, 4]
      enddef
    endclass
    var a = A.new()
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)

  # Test for assigning a new value to a final variable of composite type from
  # another function
  lines =<< trim END
    vim9script
    class A
      public final l: list<number> = [1, 2]
    endclass
    def Foo()
      var a = A.new()
      a.l = [3, 4]
    enddef
    Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 2)

  # Error case: Use 'final' with just a variable name
  lines =<< trim END
    vim9script
    class A
      final foo
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)

  # Error case: Use 'final' followed by 'public'
  lines =<< trim END
    vim9script
    class A
      final public foo: number
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)

  # Error case: Use 'final' followed by 'static'
  lines =<< trim END
    vim9script
    class A
      final static foo: number
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)

  # Error case: 'final' cannot be used in an interface
  lines =<< trim END
    vim9script
    interface A
      final foo: number = 10
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1408: Final variable not supported in an interface', 3)

  # Error case: 'final' not supported for an object method
  lines =<< trim END
    vim9script
    class A
      final def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)

  # Error case: 'final' not supported for a class method
  lines =<< trim END
    vim9script
    class A
      static final def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
enddef

" Test for 'const' class and object variables
def Test_const_class_object_variable()
  # Test for changing a const object variable from an object function
  var lines =<< trim END
    vim9script
    class A
      const foo: string = "abc"
      def Foo()
        this.foo = "def"
      enddef
    endclass
    defcompile A.Foo
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "foo" in class "A"', 1)

  # Test for changing a const object variable from the 'new' function
  lines =<< trim END
    vim9script
    class A
      const s1: string
      const s2: string
      def new(this.s1)
        this.s2 = 'def'
      enddef
    endclass
    var a = A.new('abc')
    assert_equal('abc', a.s1)
    assert_equal('def', a.s2)
  END
  v9.CheckSourceSuccess(lines)

  # Test for changing a const object variable from an object method called from
  # the 'new' function
  lines =<< trim END
    vim9script
    class A
      const s1: string = 'abc'
      def new()
        this.ChangeStr()
      enddef
      def ChangeStr()
        this.s1 = 'def'
      enddef
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)

  # Test for a const class variable
  lines =<< trim END
    vim9script
    class A
      static const s1: string = "abc"
    endclass
    assert_equal('abc', A.s1)
  END
  v9.CheckSourceSuccess(lines)

  # Test for changing a const class variable from a class function
  lines =<< trim END
    vim9script
    class A
      static const s1: string = "abc"
      static def Foo()
        s1 = "def"
      enddef
    endclass
    A.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)

  # Test for changing a public const class variable at script level
  lines =<< trim END
    vim9script
    class A
      public static const s1: string = "abc"
    endclass
    assert_equal('abc', A.s1)
    A.s1 = 'def'
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 6)

  # Test for changing a public const class variable from a class function
  lines =<< trim END
    vim9script
    class A
      public static const s1: string = "abc"
      static def Foo()
        s1 = "def"
      enddef
    endclass
    A.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)

  # Test for changing a public const class variable from a function
  lines =<< trim END
    vim9script
    class A
      public static const s1: string = "abc"
    endclass
    def Foo()
      A.s1 = 'def'
    enddef
    defcompile
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)

  # Test for changing a const List item from an object function
  lines =<< trim END
    vim9script
    class A
      public const l: list<number>
      def new()
        this.l = [1, 2]
      enddef
      def Foo()
        this.l[0] = 3
      enddef
    endclass
    var a = A.new()
    assert_equal([1, 2], a.l)
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1119: Cannot change locked list item', 1)

  # Test for adding a value to a const List from an object function
  lines =<< trim END
    vim9script
    class A
      public const l: list<number>
      def new()
        this.l = [1, 2]
      enddef
      def Foo()
        this.l->add(3)
      enddef
    endclass
    var a = A.new()
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E741: Value is locked: add() argument', 1)

  # Test for reassigning a const List from an object function
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
      def Foo()
        this.l = [3, 4]
      enddef
    endclass
    var a = A.new()
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)

  # Test for changing a const List item at script level
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
    endclass
    var a = A.new()
    a.l[0] = 3
  END
  v9.CheckSourceFailure(lines, 'E741: Value is locked:',  6)

  # Test for adding a value to a const List item at script level
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
    endclass
    var a = A.new()
    a.l->add(4)
  END
  v9.CheckSourceFailure(lines, 'E741: Value is locked:', 6)

  # Test for changing a const List item from a function
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
    endclass
    def Foo()
      var a = A.new()
      a.l[0] = 3
    enddef
    Foo()
  END
  v9.CheckSourceFailure(lines, 'E1119: Cannot change locked list item', 2)

  # Test for adding a value to a const List item from a function
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
    endclass
    def Foo()
      var a = A.new()
      a.l->add(4)
    enddef
    Foo()
  END
  v9.CheckSourceFailure(lines, 'E741: Value is locked: add() argument', 2)

  # Test for changing a const List item from an object method
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
      def Foo()
        this.l[0] = 3
      enddef
    endclass
    var a = A.new()
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1119: Cannot change locked list item', 1)

  # Test for adding a value to a const List item from an object method
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
      def Foo()
        this.l->add(4)
      enddef
    endclass
    var a = A.new()
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E741: Value is locked: add() argument', 1)

  # Test for reassigning a const List object variable at script level
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
    endclass
    var a = A.new()
    a.l = [3, 4]
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 6)

  # Test for reassigning a const List object variable from an object method
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
      def Foo()
        this.l = [3, 4]
      enddef
    endclass
    var a = A.new()
    a.Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)

  # Test for reassigning a const List object variable from another function
  lines =<< trim END
    vim9script
    class A
      public const l: list<number> = [1, 2]
    endclass
    def Foo()
      var a = A.new()
      a.l = [3, 4]
    enddef
    Foo()
  END
  v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 2)

  # Error case: Use 'const' with just a variable name
  lines =<< trim END
    vim9script
    class A
      const foo
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)

  # Error case: Use 'const' followed by 'public'
  lines =<< trim END
    vim9script
    class A
      const public foo: number
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)

  # Error case: Use 'const' followed by 'static'
  lines =<< trim END
    vim9script
    class A
      const static foo: number
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)

  # Error case: 'const' cannot be used in an interface
  lines =<< trim END
    vim9script
    interface A
      const foo: number = 10
    endinterface
  END
  v9.CheckSourceFailure(lines, 'E1410: Const variable not supported in an interface', 3)

  # Error case: 'const' not supported for an object method
  lines =<< trim END
    vim9script
    class A
      const def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)

  # Error case: 'const' not supported for a class method
  lines =<< trim END
    vim9script
    class A
      static const def Foo()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
enddef

" Test for compiling class/object methods using :defcompile
def Test_defcompile_class()
  # defcompile all the classes in the current script
  var lines =<< trim END
    vim9script
    class A
      def Foo()
        var i = 10
      enddef
    endclass
    class B
      def Bar()
        var i = 20
        xxx
      enddef
    endclass
    defcompile
  END
  v9.CheckSourceFailure(lines, 'E476: Invalid command: xxx', 2)

  # defcompile a specific class
  lines =<< trim END
    vim9script
    class A
      def Foo()
        xxx
      enddef
    endclass
    class B
      def Bar()
        yyy
      enddef
    endclass
    defcompile B
  END
  v9.CheckSourceFailure(lines, 'E476: Invalid command: yyy', 1)

  # defcompile a non-class
  lines =<< trim END
    vim9script
    class A
      def Foo()
      enddef
    endclass
    var X: list<number> = []
    defcompile X
  END
  v9.CheckSourceFailure(lines, 'E1061: Cannot find function X', 7)

  # defcompile a class twice
  lines =<< trim END
    vim9script
    class A
      def new()
      enddef
    endclass
    defcompile A
    defcompile A
    assert_equal('Function A.new does not need compiling', v:statusmsg)
  END
  v9.CheckSourceSuccess(lines)

  # defcompile should not compile an imported class
  lines =<< trim END
    vim9script
    export class A
      def Foo()
        xxx
      enddef
    endclass
  END
  writefile(lines, 'Xdefcompileimport.vim', 'D')
  lines =<< trim END
    vim9script

    import './Xdefcompileimport.vim'
    class B
    endclass
    defcompile
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for cases common to all the object builtin methods
def Test_object_builtin_method()
  var lines =<< trim END
    vim9script
    class A
      def abc()
      enddef
    endclass
  END
  v9.CheckSourceFailure(lines, 'E1267: Function name must start with a capital: abc()', 3)

  for funcname in ["len", "string", "empty"]
    lines =<< trim eval END
      vim9script
      class A
        static def {funcname}(): number
        enddef
      endclass
    END
    v9.CheckSourceFailure(lines, 'E1413: Builtin class method not supported', 3)
  endfor
enddef

" Test for using the empty() builtin method with an object
" This is a legacy function to use the test_garbagecollect_now() function.
func Test_object_empty()
  let lines =<< trim END
    vim9script
    class A
      def empty(): bool
        return true
      enddef
    endclass

    def Foo()
      var afoo = A.new()
      assert_equal(true, empty(afoo))
      assert_equal(true, afoo->empty())
    enddef

    var a = A.new()
    assert_equal(1, empty(a))
    assert_equal(1, a->empty())
    test_garbagecollect_now()
    assert_equal(1, empty(a))
    Foo()
    test_garbagecollect_now()
    Foo()
  END
  call v9.CheckSourceSuccess(lines)

  " empty() should return 1 without a builtin method
  let lines =<< trim END
    vim9script
    class A
    endclass

    def Foo()
      var afoo = A.new()
      assert_equal(1, empty(afoo))
    enddef

    var a = A.new()
    assert_equal(1, empty(a))
    Foo()
  END
  call v9.CheckSourceSuccess(lines)

  " Unsupported signature for the empty() method
  let lines =<< trim END
    vim9script
    class A
      def empty()
      enddef
    endclass
  END
  call v9.CheckSourceFailure(lines, 'E1383: Method "empty": type mismatch, expected func(): bool but got func()', 4)

  " Error when calling the empty() method
  let lines =<< trim END
    vim9script
    class A
      def empty(): bool
        throw "Failed to check emptiness"
      enddef
    endclass

    def Foo()
      var afoo = A.new()
      var i = empty(afoo)
    enddef

    var a = A.new()
    assert_fails('empty(a)', 'Failed to check emptiness')
    assert_fails('Foo()', 'Failed to check emptiness')
  END
  call v9.CheckSourceSuccess(lines)

  " call empty() using an object from a script
  let lines =<< trim END
    vim9script
    class A
      def empty(): bool
        return true
      enddef
    endclass
    var afoo = A.new()
    assert_equal(true, afoo.empty())
  END
  call v9.CheckSourceSuccess(lines)

  " call empty() using an object from a method
  let lines =<< trim END
    vim9script
    class A
      def empty(): bool
        return true
      enddef
    endclass
    def Foo()
      var afoo = A.new()
      assert_equal(true, afoo.empty())
    enddef
    Foo()
  END
  call v9.CheckSourceSuccess(lines)

  " call empty() using "this" from an object method
  let lines =<< trim END
    vim9script
    class A
      def empty(): bool
        return true
      enddef
      def Foo(): bool
        return this.empty()
      enddef
    endclass
    def Bar()
      var abar = A.new()
      assert_equal(true, abar.Foo())
    enddef
    Bar()
  END
  call v9.CheckSourceSuccess(lines)

  " Call empty() from a derived object
  let lines =<< trim END
    vim9script
    class A
      def empty(): bool
        return false
      enddef
    endclass
    class B extends A
      def empty(): bool
        return true
      enddef
    endclass
    def Foo(afoo: A)
      assert_equal(true, empty(afoo))
      var bfoo = B.new()
      assert_equal(true, empty(bfoo))
    enddef
    var b = B.new()
    assert_equal(1, empty(b))
    Foo(b)
  END
  call v9.CheckSourceSuccess(lines)

  " Invoking empty method using an interface
  let lines =<< trim END
    vim9script
    interface A
      def empty(): bool
    endinterface
    class B implements A
      def empty(): bool
        return false
      enddef
    endclass
    def Foo(a: A)
      assert_equal(false, empty(a))
    enddef
    var b = B.new()
    Foo(b)
  END
  call v9.CheckSourceSuccess(lines)
endfunc

" Test for using the len() builtin method with an object
" This is a legacy function to use the test_garbagecollect_now() function.
func Test_object_length()
  let lines =<< trim END
    vim9script
    class A
      var mylen: number = 0
      def new(n: number)
        this.mylen = n
      enddef
      def len(): number
        return this.mylen
      enddef
    endclass

    def Foo()
      var afoo = A.new(12)
      assert_equal(12, len(afoo))
      assert_equal(12, afoo->len())
    enddef

    var a = A.new(22)
    assert_equal(22, len(a))
    assert_equal(22, a->len())
    test_garbagecollect_now()
    assert_equal(22, len(a))
    Foo()
    test_garbagecollect_now()
    Foo()
  END
  call v9.CheckSourceSuccess(lines)

  " len() should return 0 without a builtin method
  let lines =<< trim END
    vim9script
    class A
    endclass

    def Foo()
      var afoo = A.new()
      assert_equal(0, len(afoo))
    enddef

    var a = A.new()
    assert_equal(0, len(a))
    Foo()
  END
  call v9.CheckSourceSuccess(lines)

  " Unsupported signature for the len() method
  let lines =<< trim END
    vim9script
    class A
      def len()
      enddef
    endclass
  END
  call v9.CheckSourceFailure(lines, 'E1383: Method "len": type mismatch, expected func(): number but got func()', 4)

  " Error when calling the len() method
  let lines =<< trim END
    vim9script
    class A
      def len(): number
        throw "Failed to compute length"
      enddef
    endclass

    def Foo()
      var afoo = A.new()
      var i = len(afoo)
    enddef

    var a = A.new()
    assert_fails('len(a)', 'Failed to compute length')
    assert_fails('Foo()', 'Failed to compute length')
  END
  call v9.CheckSourceSuccess(lines)

  " call len() using an object from a script
  let lines =<< trim END
    vim9script
    class A
      def len(): number
        return 5
      enddef
    endclass
    var afoo = A.new()
    assert_equal(5, afoo.len())
  END
  call v9.CheckSourceSuccess(lines)

  " call len() using an object from a method
  let lines =<< trim END
    vim9script
    class A
      def len(): number
        return 5
      enddef
    endclass
    def Foo()
      var afoo = A.new()
      assert_equal(5, afoo.len())
    enddef
    Foo()
  END
  call v9.CheckSourceSuccess(lines)

  " call len() using "this" from an object method
  let lines =<< trim END
    vim9script
    class A
      def len(): number
        return 8
      enddef
      def Foo(): number
        return this.len()
      enddef
    endclass
    def Bar()
      var abar = A.new()
      assert_equal(8, abar.Foo())
    enddef
    Bar()
  END
  call v9.CheckSourceSuccess(lines)

  " Call len() from a derived object
  let lines =<< trim END
    vim9script
    class A
      def len(): number
        return 10
      enddef
    endclass
    class B extends A
      def len(): number
        return 20
      enddef
    endclass
    def Foo(afoo: A)
      assert_equal(20, len(afoo))
      var bfoo = B.new()
      assert_equal(20, len(bfoo))
    enddef
    var b = B.new()
    assert_equal(20, len(b))
    Foo(b)
  END
  call v9.CheckSourceSuccess(lines)

  " Invoking len method using an interface
  let lines =<< trim END
    vim9script
    interface A
      def len(): number
    endinterface
    class B implements A
      def len(): number
        return 123
      enddef
    endclass
    def Foo(a: A)
      assert_equal(123, len(a))
    enddef
    var b = B.new()
    Foo(b)
  END
  call v9.CheckSourceSuccess(lines)
endfunc

" Test for using the string() builtin method with an object
" This is a legacy function to use the test_garbagecollect_now() function.
func Test_object_string()
  let lines =<< trim END
    vim9script
    class A
      var name: string
      def string(): string
        return this.name
      enddef
    endclass

    def Foo()
      var afoo = A.new("foo-A")
      assert_equal('foo-A', string(afoo))
      assert_equal('foo-A', afoo->string())
    enddef

    var a = A.new("script-A")
    assert_equal('script-A', string(a))
    assert_equal('script-A', a->string())
    assert_equal(['script-A'], execute('echo a')->split("\n"))
    test_garbagecollect_now()
    assert_equal('script-A', string(a))
    Foo()
    test_garbagecollect_now()
    Foo()
  END
  call v9.CheckSourceSuccess(lines)

  " string() should return "object of A {}" without a builtin method
  let lines =<< trim END
    vim9script
    class A
    endclass

    def Foo()
      var afoo = A.new()
      assert_equal('object of A {}', string(afoo))
    enddef

    var a = A.new()
    assert_equal('object of A {}', string(a))
    Foo()
  END
  call v9.CheckSourceSuccess(lines)

  " Unsupported signature for the string() method
  let lines =<< trim END
    vim9script
    class A
      def string()
      enddef
    endclass
  END
  call v9.CheckSourceFailure(lines, 'E1383: Method "string": type mismatch, expected func(): string but got func()', 4)

  " Error when calling the string() method
  let lines =<< trim END
    vim9script
    class A
      def string(): string
        throw "Failed to get text"
      enddef
    endclass

    def Foo()
      var afoo = A.new()
      var i = string(afoo)
    enddef

    var a = A.new()
    assert_fails('string(a)', 'Failed to get text')
    assert_fails('Foo()', 'Failed to get text')
  END
  call v9.CheckSourceSuccess(lines)

  " call string() using an object from a script
  let lines =<< trim END
    vim9script
    class A
      def string(): string
        return 'A'
      enddef
    endclass
    var afoo = A.new()
    assert_equal('A', afoo.string())
  END
  call v9.CheckSourceSuccess(lines)

  " call string() using an object from a method
  let lines =<< trim END
    vim9script
    class A
      def string(): string
        return 'A'
      enddef
    endclass
    def Foo()
      var afoo = A.new()
      assert_equal('A', afoo.string())
    enddef
    Foo()
  END
  call v9.CheckSourceSuccess(lines)

  " call string() using "this" from an object method
  let lines =<< trim END
    vim9script
    class A
      def string(): string
        return 'A'
      enddef
      def Foo(): string
        return this.string()
      enddef
    endclass
    def Bar()
      var abar = A.new()
      assert_equal('A', abar.string())
    enddef
    Bar()
  END
  call v9.CheckSourceSuccess(lines)

  " Call string() from a derived object
  let lines =<< trim END
    vim9script
    class A
      def string(): string
        return 'A'
      enddef
    endclass
    class B extends A
      def string(): string
        return 'B'
      enddef
    endclass
    def Foo(afoo: A)
      assert_equal('B', string(afoo))
      var bfoo = B.new()
      assert_equal('B', string(bfoo))
    enddef
    var b = B.new()
    assert_equal('B', string(b))
    Foo(b)
  END
  call v9.CheckSourceSuccess(lines)

  " Invoking string method using an interface
  let lines =<< trim END
    vim9script
    interface A
      def string(): string
    endinterface
    class B implements A
      def string(): string
        return 'B'
      enddef
    endclass
    def Foo(a: A)
      assert_equal('B', string(a))
    enddef
    var b = B.new()
    Foo(b)
  END
  call v9.CheckSourceSuccess(lines)
endfunc

" Test for using the string() builtin method with an object's method
def Test_method_string()
  var lines =<< trim END
    vim9script
    class A
      def F()
      enddef
    endclass
    assert_match('function(''<SNR>\d\+_A\.F'')', string(A.new().F))
  END
  v9.CheckScriptSuccess(lines)
enddef


" Test for using a class in the class definition
def Test_Ref_Class_Within_Same_Class()
  var lines =<< trim END
    vim9script
    class A
      var n: number = 0
      def Equals(other: A): bool
        return this.n == other.n
      enddef
    endclass

    var a1 = A.new(10)
    var a2 = A.new(10)
    var a3 = A.new(20)
    assert_equal(true, a1.Equals(a2))
    assert_equal(false, a2.Equals(a3))
  END
  v9.CheckScriptSuccess(lines)

  lines =<< trim END
    vim9script

    class Foo
      var num: number
      def Clone(): Foo
        return Foo.new(this.num)
      enddef
    endclass

    var f1 = Foo.new(1)

    def F()
      var f2: Foo = f1.Clone()
      assert_equal(false, f2 is f1)
      assert_equal(true, f2.num == f1.num)
    enddef
    F()

    var f3: Foo = f1.Clone()
    assert_equal(false, f3 is f1)
    assert_equal(true, f3.num == f1.num)
  END
  v9.CheckScriptSuccess(lines)

  # Test for trying to use a class to extend when defining the same class
  lines =<< trim END
    vim9script
    class A extends A
    endclass
  END
  v9.CheckScriptFailure(lines, 'E1354: Cannot extend A', 3)

  # Test for trying to use a class to implement when defining the same class
  lines =<< trim END
    vim9script
    class A implements A
    endclass
  END
  v9.CheckScriptFailure(lines, 'E1347: Not a valid interface: A', 3)
enddef

" Test for comparing a class referencing itself
def Test_Object_Compare_With_Recursive_Class_Ref()
  var lines =<< trim END
    vim9script

    class C
    public var nest: C
    endclass

    var o1 = C.new()
    o1.nest = o1

    var result = o1 == o1
    assert_equal(true, result)
  END
  v9.CheckScriptSuccess(lines)

  lines =<< trim END
    vim9script

    class C
        public var nest: C
    endclass
    var o1 = C.new()
    var o2 = C.new(C.new())

    var result = o1 == o2
    assert_equal(false, result)
  END
  v9.CheckScriptSuccess(lines)

  lines =<< trim END
    vim9script
    class C
        var nest1: C
        var nest2: C
        def Init(n1: C, n2: C)
            this.nest1 = n1
            this.nest2 = n2
        enddef
    endclass

    var o1 = C.new()
    var o2 = C.new()
    o1.Init(o1, o2)
    o2.Init(o2, o1)

    var result = o1 == o2
    assert_equal(true, result)
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for comparing a class with nesting objects
def Test_Object_Compare_With_Nesting_Objects()
  # On a compare, after vim equal recurses 1000 times, not finding an unequal,
  # return the compare is equal.
  # Test that limit

  var lines =<< trim END
    vim9script
    class C
      public var n: number
      public var nest: C

      # Create a "C" that chains/nests to indicated depth.
      # return {head: firstC, tail: lastC}
      static def CreateNested(depth: number): dict<C>
        var first = C.new(1, null_object)
        var last = first
        for i in range(2, depth)
          last.nest = C.new(i, null_object)
          last = last.nest
        endfor
        return {head: first, tail: last}
      enddef

      # Return pointer to nth item in chain.
      def GetLink(depth: number): C
        var count = 1
        var p: C = this
        while count < depth
          p = p.nest
          if p == null
            throw "too deep"
          endif
          count += 1
        endwhile
        return p
      enddef

      # Return the length of the chain
      def len(): number
        var count = 1
        var p: C = this
        while p.nest != null
          p = p.nest
          count += 1
        endwhile
        return count
      enddef
    endclass

    var chain = C.CreateNested(3)
    var s = "object of C {n: 1, nest: object of C {n: 2, nest: object of C {n: 3, nest: object of [unknown]}}}"
    assert_equal(s, string(chain.head))
    assert_equal(3, chain.head->len())

    var chain1 = C.CreateNested(100)
    var chain2 = C.CreateNested(100)
    assert_true(chain1.head == chain2.head)

    # modify the tail of chain2, compare not equal
    chain2.tail.n = 123456
    assert_true(chain1.head != chain2.head)

    # a tail of a different length compares not equal
    chain2 = C.CreateNested(101)
    assert_true(chain1.head != chain2.head)

    chain1 = C.CreateNested(1000)
    chain2 = C.CreateNested(1000)
    assert_true(chain1.head == chain2.head)

    # modify the tail of chain2, compare not equal
    chain2.tail.n = 123456
    assert_true(chain1.head != chain2.head)

    # try a chain longer that the limit
    chain1 = C.CreateNested(1001)
    chain2 = C.CreateNested(1001)
    assert_true(chain1.head == chain2.head)

    # modify the tail, but still equal
    chain2.tail.n = 123456
    assert_true(chain1.head == chain2.head)

    # remove 2 items from front, shorten the chain by two.
    chain1.head = chain1.head.GetLink(3)
    chain2.head = chain2.head.GetLink(3)
    assert_equal(3, chain1.head.n)
    assert_equal(3, chain2.head.n)
    assert_equal(999, chain1.head->len())
    assert_equal(999, chain2.head->len())
    # Now less than the limit, compare not equal
    assert_true(chain1.head != chain2.head)
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for using a compound operator from a lambda function in an object method
def Test_compound_op_in_objmethod_lambda()
  # Test using the "+=" operator
  var lines =<< trim END
    vim9script
    class A
      var n: number = 10
      def Foo()
        var Fn = () => {
          this.n += 1
        }
        Fn()
      enddef
    endclass

    var a = A.new()
    a.Foo()
    assert_equal(11, a.n)
  END
  v9.CheckScriptSuccess(lines)

  # Test using the "..=" operator
  lines =<< trim END
    vim9script
    class A
      var s: string = "a"
      def Foo()
        var Fn = () => {
          this.s ..= "a"
        }
        Fn()
      enddef
    endclass

    var a = A.new()
    a.Foo()
    a.Foo()
    assert_equal("aaa", a.s)
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for using test_refcount() with a class and an object
def Test_class_object_refcount()
  var lines =<< trim END
    vim9script
    class A
    endclass
    var a: A = A.new()
    assert_equal(2, test_refcount(A))
    assert_equal(1, test_refcount(a))
    var b = a
    assert_equal(2, test_refcount(A))
    assert_equal(2, test_refcount(a))
    assert_equal(2, test_refcount(b))
  END
  v9.CheckScriptSuccess(lines)
enddef

" call a lambda function in one object from another object
def Test_lambda_invocation_across_classes()
  var lines =<< trim END
    vim9script
    class A
      var s: string = "foo"
      def GetFn(): func
        var Fn = (): string => {
          return this.s
        }
        return Fn
      enddef
    endclass

    class B
      var s: string = "bar"
      def GetFn(): func
        var a = A.new()
        return a.GetFn()
      enddef
    endclass

    var b = B.new()
    var Fn = b.GetFn()
    assert_equal("foo", Fn())
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for using a class member which is an object of the current class
def Test_current_class_object_class_member()
  var lines =<< trim END
    vim9script
    class A
      public static var obj1: A = A.new(10)
      var n: number
    endclass
    defcompile
    assert_equal(10, A.obj1.n)
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for updating a base class variable from a base class method without the
" class name.  This used to crash Vim (Github issue #14352).
def Test_use_base_class_variable_from_base_class_method()
  var lines =<< trim END
    vim9script

    class DictKeyClass
      static var _obj_id_count = 1
      def _GenerateKey()
        _obj_id_count += 1
      enddef
      static def GetIdCount(): number
        return _obj_id_count
      enddef
    endclass

    class C extends DictKeyClass
      def F()
        this._GenerateKey()
      enddef
    endclass

    C.new().F()
    assert_equal(2, DictKeyClass.GetIdCount())
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for accessing protected funcref object and class variables
def Test_protected_funcref()
  # protected funcref object variable
  var lines =<< trim END
    vim9script
    class Test1
      const _Id: func(any): any = (v) => v
    endclass
    var n = Test1.new()._Id(1)
  END
  v9.CheckScriptFailure(lines, 'E1333: Cannot access protected variable "_Id" in class "Test1"', 5)

  # protected funcref class variable
  lines =<< trim END
    vim9script
    class Test2
      static const _Id: func(any): any = (v) => v
    endclass
    var n = Test2._Id(2)
  END
  v9.CheckScriptFailure(lines, 'E1333: Cannot access protected variable "_Id" in class "Test2"', 5)
enddef

" Test for using lambda block in classes
def Test_lambda_block_in_class()
  # This used to crash Vim
  var lines =<< trim END
    vim9script
    class IdClass1
      const Id: func(number): number = (num: number): number => {
        # Return a ID
        return num * 10
      }
    endclass
    var id = IdClass1.new()
    assert_equal(20, id.Id(2))
  END
  v9.CheckScriptSuccess(lines)

  # This used to crash Vim
  lines =<< trim END
    vim9script
    class IdClass2
      static const Id: func(number): number = (num: number): number => {
        # Return a ID
        return num * 2
      }
    endclass
    assert_equal(16, IdClass2.Id(8))
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for defcompiling an abstract method
def Test_abstract_method_defcompile()
  # Compile an abstract class with abstract object methods
  var lines =<< trim END
    vim9script
    abstract class A
      abstract def Foo(): string
      abstract def Bar(): list<string>
    endclass
    defcompile
  END
  v9.CheckScriptSuccess(lines)

  # Compile a concrete object method in an abstract class
  lines =<< trim END
    vim9script
    abstract class A
      abstract def Foo(): string
      abstract def Bar(): list<string>
      def Baz(): string
        pass
      enddef
    endclass
    defcompile
  END
  v9.CheckScriptFailure(lines, 'E476: Invalid command: pass', 1)

  # Compile a concrete class method in an abstract class
  lines =<< trim END
    vim9script
    abstract class A
      abstract def Foo(): string
      abstract def Bar(): list<string>
      static def Baz(): string
        pass
      enddef
    endclass
    defcompile
  END
  v9.CheckScriptFailure(lines, 'E476: Invalid command: pass', 1)
enddef

" Test for defining a class in a function
def Test_class_definition_in_a_function()
  var lines =<< trim END
    vim9script
    def Foo()
      class A
      endclass
    enddef
    defcompile
  END
  v9.CheckScriptFailure(lines, 'E1429: Class can only be used in a script', 1)
enddef

" Test for using [] with a class and an object
def Test_class_object_index()
  var lines =<< trim END
    vim9script
    class A
    endclass
    A[10] = 1
  END
  v9.CheckScriptFailure(lines, 'E689: Index not allowed after a class: A[10] = 1', 4)

  lines =<< trim END
    vim9script
    class A
    endclass
    var a = A.new()
    a[10] = 1
  END
  v9.CheckScriptFailure(lines, 'E689: Index not allowed after a object: a[10] = 1', 5)
enddef

def Test_class_member_init_typecheck()
  # Ensure the class member is assigned its declared type.
  var lines =<< trim END
    vim9script
    class S
        static var l: list<string> = []
    endclass
    S.l->add(123)
  END
  v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected string but got number', 5)

  # Ensure the initializer value and the declared type match.
  lines =<< trim END
    vim9script
    class S
        var l: list<string> = [1, 2, 3]
    endclass
    var o = S.new()
  END
  v9.CheckScriptFailure(lines, 'E1382: Variable "l": type mismatch, expected list<string> but got list<number>')

  # Ensure the class member is assigned its declared type.
  lines =<< trim END
    vim9script
    class S
        var l: list<string> = []
    endclass
    var o = S.new()
    o.l->add(123)
  END
  v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected string but got number', 6)
enddef

def Test_class_cast()
  var lines =<< trim END
    vim9script
    class A
    endclass
    class B extends A
      var mylen: number
    endclass
    def F(o: A): number
      return (<B>o).mylen
    enddef

    defcompile F
  END
  v9.CheckScriptSuccess(lines)
enddef

" Test for using a variable of type "any" with an object
def Test_any_obj_var_type()
  var lines =<< trim END
    vim9script
    class A
      var name: string = "foobar"
      def Foo(): string
        return "func foo"
      enddef
    endclass

    def CheckVals(x: any)
      assert_equal("foobar", x.name)
      assert_equal("func foo", x.Foo())
    enddef

    var a = A.new()
    CheckVals(a)
  END
  v9.CheckScriptSuccess(lines)

  # Try to set a non-existing variable
  lines =<< trim END
    vim9script
    class A
      var name: string = "foobar"
    endclass

    def SetNonExistingVar(x: any)
      x.bar = [1, 2, 3]
    enddef

    var a = A.new()
    SetNonExistingVar(a)
  END
  v9.CheckScriptFailure(lines, 'E1326: Variable "bar" not found in object "A"', 1)

  # Try to read a non-existing variable
  lines =<< trim END
    vim9script
    class A
      var name: string = "foobar"
    endclass

    def GetNonExistingVar(x: any)
      var i: dict<any> = x.bar
    enddef

    var a = A.new()
    GetNonExistingVar(a)
  END
  v9.CheckScriptFailure(lines, 'E1326: Variable "bar" not found in object "A"', 1)

  # Try to invoke a non-existing method
  lines =<< trim END
    vim9script
    class A
      def Foo(): number
        return 10
      enddef
    endclass

    def CallNonExistingMethod(x: any)
      var i: number = x.Bar()
    enddef

    var a = A.new()
    CallNonExistingMethod(a)
  END
  v9.CheckScriptFailure(lines, 'E1326: Variable "Bar" not found in object "A"', 1)

  # Use an object which is a Dict value
  lines =<< trim END
    vim9script
    class Foo
      def Bar(): number
        return 369
      enddef
    endclass

    def GetValue(FooDict: dict<any>): number
      var n: number = 0
      for foo in values(FooDict)
        n += foo.Bar()
      endfor
      return n
    enddef

    var d = {'x': Foo.new()}
    assert_equal(369, GetValue(d))
  END
  v9.CheckScriptSuccess(lines)

  # Nested data.  Object containing a Dict containing another Object.
  lines =<< trim END
    vim9script
    class Context
      public var state: dict<any> = {}
    endclass

    class Metadata
      public var value = 0
    endclass

    var ctx = Context.new()
    ctx.state["meta"] = Metadata.new(2468)

    const foo = ctx.state.meta.value

    def F(): number
      const bar = ctx.state.meta.value
      return bar
    enddef

    assert_equal(2468, F())
  END
  v9.CheckScriptSuccess(lines)

  # Accessing an object from a method inside the class using any type
  lines =<< trim END
    vim9script
    class C
      def _G(): string
        return '_G'
      enddef
      static def S(o_any: any): string
        return o_any._G()
      enddef
    endclass

    var o1 = C.new()
    assert_equal('_G', C.S(o1))
  END
  v9.CheckScriptSuccess(lines)

  # Modifying an object private variable from a method in another class using
  # any type
  lines =<< trim END
    vim9script

    class A
      var num = 10
    endclass

    class B
      def SetVal(x: any)
        x.num = 20
      enddef
    endclass

    var a = A.new()
    var b = B.new()
    b.SetVal(a)
  END
  v9.CheckScriptFailure(lines, 'E1335: Variable "num" in class "A" is not writable', 1)

  # Accessing a object protected variable from a method in another class using
  # any type
  lines =<< trim END
    vim9script

    class A
      var _num = 10
    endclass

    class B
      def GetVal(x: any): number
        return x._num
      enddef
    endclass

    var a = A.new()
    var b = B.new()
    var i = b.GetVal(a)
  END
  v9.CheckScriptFailure(lines, 'E1333: Cannot access protected variable "_num" in class "A"', 1)

  # Accessing an object returned from an imported function and class
  lines =<< trim END
    vim9script
    export class Foo
      public var name: string
    endclass

    export def ReturnFooObject(): Foo
      var r = Foo.new('star')
      return r
    enddef
  END
  writefile(lines, 'Xanyvar1.vim', 'D')

  lines =<< trim END
    vim9script

    import './Xanyvar1.vim'

    def GetName(): string
      var whatever = Xanyvar1.ReturnFooObject()
      return whatever.name
    enddef

    assert_equal('star', GetName())
  END
  v9.CheckScriptSuccess(lines)

  # Try to modify a private object variable using a variable of type "any"
  lines =<< trim END
    vim9script

    class Foo
      var n: number = 10
    endclass
    def Fn(x: any)
      x.n = 20
    enddef
    var a = Foo.new()
    Fn(a)
  END
  v9.CheckScriptFailure(lines, 'E1335: Variable "n" in class "Foo" is not writable', 1)

  # Try to read a protected object variable using a variable of type "any"
  lines =<< trim END
    vim9script

    class Foo
      var _n: number = 10
    endclass
    def Fn(x: any): number
      return x._n
    enddef

    var a = Foo.new()
    Fn(a)
  END
  v9.CheckScriptFailure(lines, 'E1333: Cannot access protected variable "_n" in class "Foo"', 1)

  # Read a protected object variable using a variable of type "any" in an object
  # method
  lines =<< trim END
    vim9script

    class Foo
      var _n: number = 10
      def Fn(x: any): number
        return x._n
      enddef
    endclass

    var a = Foo.new()
    assert_equal(10, a.Fn(a))
  END
  v9.CheckScriptSuccess(lines)

  # Try to call a protected object method using a "any" type variable
  lines =<< trim END
    vim9script

    class Foo
      def _GetVal(): number
        return 234
      enddef
    endclass
    def Fn(x: any): number
      return x._GetVal()
    enddef

    var a = Foo.new()
    Fn(a)
  END
  v9.CheckScriptFailure(lines, 'E1366: Cannot access protected method: _GetVal', 1)

  # Call a protected object method using a "any" type variable from another
  # object method
  lines =<< trim END
    vim9script

    class Foo
      def _GetVal(): number
        return 234
      enddef
      def FooVal(x: any): number
        return x._GetVal()
      enddef
    endclass

    var a = Foo.new()
    assert_equal(234, a.FooVal(a))
  END
  v9.CheckScriptSuccess(lines)

  # Method chaining
  lines =<< trim END
    vim9script

    export class T
      var id: number = 268
      def F(): any
        return this
      enddef
    endclass

    def H()
      var a = T.new().F().F()
      assert_equal(268, a.id)
    enddef
    H()

    var b: T = T.new().F().F()
    assert_equal(268, b.id)
  END
  v9.CheckScriptSuccess(lines)

  # Using a null object to access a member variable
  lines =<< trim END
    vim9script
    def Fn(x: any): number
      return x.num
    enddef

    Fn(null_object)
  END
  v9.CheckScriptFailure(lines, 'E1360: Using a null object', 1)

  # Using a null object to invoke a method
  lines =<< trim END
    vim9script
    def Fn(x: any)
      x.Foo()
    enddef

    Fn(null_object)
  END
  v9.CheckScriptFailure(lines, 'E1360: Using a null object', 1)

  # Try to change a const object variable using a "any" variable
  lines =<< trim END
    vim9script
    class A
      public const v1: number = 123
    endclass

    def Fn(o: any)
      o.v1 = 321
    enddef

    var a = A.new()
    Fn(a)
  END
  v9.CheckScriptFailure(lines, 'E1409: Cannot change read-only variable "v1" in class "A"', 1)

  # Try to change a final object variable using a "any" variable
  lines =<< trim END
    vim9script
    class A
      public final v1: number = 123
    endclass

    def Fn(o: any)
      o.v1 = 321
    enddef

    var a = A.new()
    Fn(a)
  END
  v9.CheckScriptFailure(lines, 'E1409: Cannot change read-only variable "v1" in class "A"', 1)

  # Assign a different type of value to an "any" type object variable
  lines =<< trim END
    vim9script
    class A
      public var v1: list<any> = [1, 2]
    endclass

    def Fn(o: A)
      o.v1 = 'abc'
    enddef

    var a = A.new()
    Fn(a)
  END
  v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected list<any> but got string', 1)
enddef

" Test for using an object method with mapnew()
def Test_mapnew_with_instance_method()
  var lines =<< trim END
    vim9script

    class Foo
      var str: string
      var nums: list<number> = [1, 2, 3]

      def InstanceMethod(n: number): string
        return this.str .. n
      enddef

      def MapperMethod(idx: number, elem: number): string
        return elem->this.InstanceMethod()
      enddef

      def MapTest()
        this.str = "foo"
        var l = ['foo1', 'foo2', 'foo3']
        assert_equal(l, this.nums->mapnew(this.MapperMethod))
      enddef
    endclass

    Foo.new().MapTest()
  END
  v9.CheckSourceSuccess(lines)

  # Error in the mapnew() function
  lines =<< trim END
    vim9script

    class Foo
      var str: string
      var nums: list<number> = [1, 2, 3]

      def InstanceMethod(n: number): string
        throw "InstanceMethod failed"
      enddef

      def MapperMethod(idx: number, elem: number): string
        return elem->this.InstanceMethod()
      enddef

      def MapTest()
        this.str = "foo"
        var caught_exception: bool = false
        try
          this.nums->mapnew(this.MapperMethod)
        catch /InstanceMethod failed/
          caught_exception = true
        endtry
        assert_true(caught_exception)
      enddef
    endclass

    Foo.new().MapTest()
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using an object method in a method call.
def Test_use_object_method_in_a_method_call()
  var lines =<< trim END
    vim9script

    class Foo
      def Cost(nums: list<number>): number
        return nums[0] * nums[1]
      enddef

      def ShowCost(): string
        var g = [4, 5]
        return $"Cost is: {g->this.Cost()}"
      enddef
    endclass

    var d = Foo.new()
    assert_equal('Cost is: 20', d.ShowCost())
  END
  v9.CheckSourceSuccess(lines)

  # Test for using a non-existing object method in string interpolation
  lines =<< trim END
    vim9script

    class Foo
      def Cost(nums: list<number>): number
        return nums[0] * nums[1]
      enddef

      def ShowCost(): string
        var g = [4, 5]
        echo $"Cost is: {g->this.NewCost()}"
      enddef
    endclass

    var d = Foo.new()
    d.ShowCost()
  END
  v9.CheckSourceFailure(lines, 'E1326: Variable "NewCost" not found in object "Foo"')
enddef

" Test for referencing an object variable which is not yet initialized
def Test_uninitialized_object_var()
  var lines =<< trim END
    vim9script
    class Foo
      const two: number = Foo.Two(this)
      const one: number = 1

      static def Two(that: Foo): number
        return that.one + 2
      enddef
    endclass

    echo Foo.Two(Foo.new())
  END
  v9.CheckSourceFailure(lines, "E1430: Uninitialized object variable 'one' referenced")

  lines =<< trim END
    vim9script
    class Foo
      const one: number = Foo.One(this)

      static def One(that: Foo): number
        return 1
      enddef
    endclass

    assert_equal(1, Foo.One(Foo.new()))
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Foo
      const one: number = 1
      const two: number = Foo.Two(this)

      static def Two(that: Foo): number
        return that.one + 1
      enddef
    endclass

    assert_equal(2, Foo.Two(Foo.new()))
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Foo
      const Id: func(any): any = ((_) => (v) => v)(this)

      static def Id(that: Foo): func(any): any
        return that.Id
      enddef
    endclass

    assert_equal(5, Foo.Id(Foo.new())(5))
    assert_equal(7, Foo.new().Id(7))
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Foo
      const Id: func(any): any = ((that) => (_) => that)(this)

      static def Id(that: Foo): func(any): any
          return that.Id
      enddef
    endclass

    const Id0: func(any): any = Foo.Id(Foo.new())
    const Id1: func(any): any = Foo.new().Id
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Foo
      const Id: any = Foo.Id(this)

      static def Id(that: Foo): any
          return that.Id
      enddef
    endclass

    const Id2: any = Foo.Id(Foo.new())
    const Id3: any = Foo.new().Id
  END
  v9.CheckSourceFailure(lines, "E1430: Uninitialized object variable 'Id' referenced")

  lines =<< trim END
    vim9script

    class Foo
      var x: string = ''
      var Y: func(): string = () => this.x
    endclass

    var foo = Foo.new('ok')
    assert_equal('ok', foo.Y())
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    class Foo
      var x: string = this.x
    endclass

    var foo = Foo.new('ok')
  END
  v9.CheckSourceFailure(lines, "E1430: Uninitialized object variable 'x' referenced")
enddef

" Test for initializing member variables of compound type in the constructor
def Test_constructor_init_compound_member_var()
  var lines =<< trim END
    vim9script

    class Foo
      var v1: string = "aaa"
      var v2: list<number> = [1, 2]
      var v3: dict<string> = {a: 'a', b: 'b'}
    endclass

    class Bar
      var v4: string = "bbb"
      var v5: Foo = Foo.new()
      var v6: list<number> = [1, 2]
    endclass

    var b: Bar = Bar.new()
    assert_equal("aaa", b.v5.v1)
    assert_equal([1, 2], b.v5.v2)
    assert_equal({a: 'a', b: 'b'}, b.v5.v3)
    assert_equal("bbb", b.v4)
    assert_equal([1, 2], b.v6)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using a concrete method in an abstract extended class which is
" further extended
def Test_abstract_method_across_hierarchy()
  var lines =<< trim END
    vim9script

    abstract class A
      abstract def Foo(): string
    endclass

    abstract class B extends A
      abstract def Bar(): string
    endclass

    class C extends B
      def Foo(): string
        return 'foo'
      enddef

      def Bar(): string
        return 'bar'
      enddef
    endclass

    def Fn1(a: A): string
      return a.Foo()
    enddef

    def Fn2(b: B): string
      return b.Bar()
    enddef

    var c = C.new()
    assert_equal('foo', Fn1(c))
    assert_equal('bar', Fn2(c))
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    abstract class A
      abstract def Foo(): string
    endclass

    abstract class B extends A
      abstract def Bar(): string
    endclass

    class C extends B
      def Bar(): string
        return 'bar'
      enddef
    endclass

    defcompile
  END
  v9.CheckSourceFailure(lines, 'E1373: Abstract method "Foo" is not implemented')

  lines =<< trim END
    vim9script

    abstract class A
      abstract def M1(): string
      abstract def M2(): string
    endclass

    abstract class B extends A
      def M1(): string
        return 'B: M1'
      enddef

      def M2(): string
        return 'B: M2'
      enddef
    endclass

    class C1 extends B
      def M1(): string
        return 'C1: M1'
      enddef
    endclass

    class C2 extends B
      def M2(): string
        return 'C2: M2'
      enddef
    endclass

    class D1 extends C1
    endclass

    class D2 extends C2
    endclass

    var l: list<string> = []
    for Type in ['C1', 'C2', 'D1', 'D2']
      l->add(eval($'{Type}.new().M1()'))
      l->add(eval($'{Type}.new().M2()'))
    endfor
    assert_equal(['C1: M1', 'B: M2', 'B: M1', 'C2: M2', 'C1: M1', 'B: M2', 'B: M1', 'C2: M2'], l)
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script

    abstract class A
      abstract def M1(): string
      abstract def M2(): string
    endclass

    class B extends A
      def M1(): string
        return 'B: M1'
      enddef

      def M2(): string
        return 'B: M2'
      enddef
    endclass

    abstract class C extends B
    endclass

    class D1 extends C
      def M1(): string
        return 'D1: M1'
      enddef
    endclass

    class D2 extends C
      def M2(): string
        return 'D2: M2'
      enddef
    endclass

    class E1 extends D1
    endclass

    class E2 extends D2
    endclass

    var l: list<string> = []
    for Type in ['B', 'D1', 'D2', 'E1', 'E2']
      l->add(eval($'{Type}.new().M1()'))
      l->add( eval($'{Type}.new().M2()'))
    endfor
    assert_equal(['B: M1', 'B: M2', 'D1: M1', 'B: M2', 'B: M1', 'D2: M2', 'D1: M1', 'B: M2', 'B: M1', 'D2: M2'], l)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using a protected new() method (singleton design pattern)
def Test_protected_new_method()
  var lines =<< trim END
    vim9script
    class A
      def _new()
      enddef
    endclass
    var a = A.new()
  END
  v9.CheckSourceFailure(lines, 'E1325: Method "new" not found in class "A"', 6)

  lines =<< trim END
    vim9script
    class A
      static var _instance: A
      var str: string
      def _new(str: string)
        this.str = str
      enddef
      static def GetInstance(str: string): A
        if _instance == null
          _instance = A._new(str)
        endif
        return _instance
      enddef
    endclass
    var a: A = A.GetInstance('foo')
    var b: A = A.GetInstance('bar')
    assert_equal('foo', a.str)
    assert_equal('foo', b.str)
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using 'super' in a closure function inside an object method
def Test_super_in_closure()
  var lines =<< trim END
    vim9script

    class A
      const _value: number

      def Fn(): func(any): number
        return (_: any) => this._value
      enddef
    endclass

    class B extends A
      def Fn(): func(any): number
        return (_: any) => super._value
      enddef
    endclass

    assert_equal(100, A.new(100).Fn()(null))
    assert_equal(200, B.new(200).Fn()(null))
  END
  v9.CheckSourceSuccess(lines)
enddef

" Test for using 'super' to access methods and variables
def Test_super_keyword()
  var lines =<< trim END
    vim9script
    class Base
      var name: string
      def ToString(): string
        return this.name
      enddef
    endclass

    class Child extends Base
      var age: number
      def ToString(): string
        return super.ToString() .. ': ' .. this.age
      enddef
    endclass

    var o = Child.new('John', 42)
    assert_equal('John: 42', o.ToString())
  END
  v9.CheckSourceSuccess(lines)

  lines =<< trim END
    vim9script
    class Child
      var age: number
      def ToString(): string
        return super .ToString() .. ': ' .. this.age
      enddef
    endclass
    var o = Child.new(42)
    echo o.ToString()
  END
  v9.CheckSourceFailure(lines, 'E1356: "super" must be followed by a dot', 1)

  lines =<< trim END
    vim9script
    class Base
      var name: string
      def ToString(): string
        return this.name
      enddef
    endclass

    var age = 42
    def ToString(): string
      return super.ToString() .. ': ' .. age
    enddef
    echo ToString()
  END
  v9.CheckSourceFailure(lines, 'E1357: Using "super" not in a class method', 1)

  lines =<< trim END
    vim9script
    class Child
      var age: number
      def ToString(): string
        return super.ToString() .. ': ' .. this.age
      enddef
    endclass
    var o = Child.new(42)
    echo o.ToString()
  END
  v9.CheckSourceFailure(lines, 'E1358: Using "super" not in a child class', 1)

  # Using super, Child invokes Base method which has optional arg. #12471
  lines =<< trim END
    vim9script

    class Base
      var success: bool = false
      def Method(arg = 0)
        this.success = true
      enddef
    endclass

    class Child extends Base
      def new()
        super.Method()
      enddef
    endclass

    var obj = Child.new()
    assert_equal(true, obj.success)
  END
  v9.CheckSourceSuccess(lines)

  # Using 'super' to access an object variable in the parent
  lines =<< trim END
    vim9script

    class A
      var foo: string = 'xxx'
    endclass

    class B extends A
      def GetString(): string
        return super.foo
      enddef
    endclass

    var b: B = B.new()
    echo b.GetString()
  END
  v9.CheckSourceSuccess(lines)

  # Using super to access an overriden method in the parent class
  lines =<< trim END
    vim9script

    class A
      def Foo(): string
        return 'A.Foo'
      enddef
    endclass

    class B extends A
      def Foo(): string
        return 'B.Foo'
      enddef

      def Bar(): string
        return $'{super.Foo()} {this.Foo()}'
      enddef
    endclass

    var b = B.new()
    assert_equal('A.Foo B.Foo', b.Bar())
  END
  v9.CheckSourceSuccess(lines)
enddef

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