Mercurial > vim
view src/testdir/test_vim9_class.vim @ 32854:5fd9fe58c791 v9.0.1737
patch 9.0.1737: Calling a base class method through an extended class fails
Commit: https://github.com/vim/vim/commit/b102728c204430b16a5d9e6720c882e12a687570
Author: Yegappan Lakshmanan <yegappan@yahoo.com>
Date: Sat Aug 19 11:26:42 2023 +0200
patch 9.0.1737: Calling a base class method through an extended class fails
Problem: Calling a base class method through an extended class fails
Solution: Create lookup table for member index in the interface to
to the member class implementing the interface
Create additional tests for Vim9 classes. Fix unconvered memory leaks
and crashes found by the new tests.
closes: #12848
closes: #12089
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>author
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Sat, 19 Aug 2023 11:45:03 +0200 |
parents | b3a42579bb3f |
children | a39314fa9495 |
line wrap: on
line source
" Test Vim9 classes source check.vim import './vim9.vim' as v9 def Test_class_basic() var lines =<< trim END class NotWorking endclass END v9.CheckScriptFailure(lines, 'E1316:') lines =<< trim END vim9script class notWorking endclass END v9.CheckScriptFailure(lines, 'E1314:') lines =<< trim END vim9script class Not@working endclass END v9.CheckScriptFailure(lines, 'E1315:') lines =<< trim END vim9script abstract noclass Something endclass END v9.CheckScriptFailure(lines, 'E475:') lines =<< trim END vim9script abstract classy Something endclass END v9.CheckScriptFailure(lines, 'E475:') lines =<< trim END vim9script class Something endcl END v9.CheckScriptFailure(lines, 'E1065:') lines =<< trim END vim9script class Something endclass school's out END v9.CheckScriptFailure(lines, 'E488:') lines =<< trim END vim9script class Something endclass | echo 'done' END v9.CheckScriptFailure(lines, 'E488:') lines =<< trim END vim9script class Something this endclass END v9.CheckScriptFailure(lines, 'E1317:') lines =<< trim END vim9script class Something this. endclass END v9.CheckScriptFailure(lines, 'E1317:') lines =<< trim END vim9script class Something this .count endclass END v9.CheckScriptFailure(lines, 'E1317:') lines =<< trim END vim9script class Something this. count endclass END v9.CheckScriptFailure(lines, 'E1317:') lines =<< trim END vim9script class Something this.count: number that.count endclass END v9.CheckScriptFailure(lines, 'E1318: Not a valid command in a class: that.count') lines =<< trim END vim9script class Something this.count endclass END v9.CheckScriptFailure(lines, 'E1022:') lines =<< trim END vim9script class Something def new() this.state = 0 enddef endclass var obj = Something.new() END v9.CheckScriptFailure(lines, 'E1089:') lines =<< trim END vim9script class Something this.count : number endclass END v9.CheckScriptFailure(lines, 'E1059:') lines =<< trim END vim9script class Something this.count:number endclass END v9.CheckScriptFailure(lines, 'E1069:') # Test for unsupported comment specifier lines =<< trim END vim9script class Something # comment #{ endclass END v9.CheckScriptFailure(lines, 'E1170:') lines =<< trim END vim9script class TextPosition this.lnum: number this.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.CheckScriptSuccess(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.CheckScriptFailure(lines, 'E1202:') # 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.CheckScriptFailure(lines, 'E15:') # 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.CheckScriptFailure(lines, 'E15:') enddef def Test_class_defined_twice() # class defined twice should fail var lines =<< trim END vim9script class There endclass class There endclass END v9.CheckScriptFailure(lines, 'E1041: Redefining script item: "There"') # 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.CheckScriptSuccess(lines) enddef def Test_using_null_class() var lines =<< trim END @_ = null_class.member END v9.CheckDefExecAndScriptFailure(lines, ['E715:', 'E1363:']) enddef def Test_class_interface_wrong_end() var lines =<< trim END vim9script abstract class SomeName this.member = 'text' endinterface END v9.CheckScriptFailure(lines, 'E476: Invalid command: endinterface, expected endclass') lines =<< trim END vim9script export interface AnotherName this.member: string endclass END v9.CheckScriptFailure(lines, 'E476: Invalid command: endclass, expected endinterface') enddef def Test_object_not_set() var lines =<< trim END vim9script class State this.value = 'xyz' endclass var state: State var db = {'xyz': 789} echo db[state.value] END v9.CheckScriptFailure(lines, 'E1360:') lines =<< trim END vim9script class Class this.id: string def Method1() echo 'Method1' .. this.id enddef endclass var obj: Class def Func() obj.Method1() enddef Func() END v9.CheckScriptFailure(lines, 'E1360:') lines =<< trim END vim9script class Background this.background = 'dark' endclass class Colorscheme this._bg: Background def GetBackground(): string return this._bg.background enddef endclass var bg: Background # UNINITIALIZED echo Colorscheme.new(bg).GetBackground() END v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected object<Background> but got object<Unknown>') # TODO: this should not give an error but be handled at runtime lines =<< trim END vim9script class Class this.id: string def Method1() echo 'Method1' .. this.id enddef endclass var obj = null_object def Func() obj.Method1() enddef Func() END v9.CheckScriptFailure(lines, 'E1363:') enddef def Test_class_member_initializer() var lines =<< trim END vim9script class TextPosition this.lnum: number = 1 this.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.CheckScriptSuccess(lines) enddef def Test_member_any_used_as_object() var lines =<< trim END vim9script class Inner this.value: number = 0 endclass class Outer this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class Inner this.value: number = 0 endclass class Outer this.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.CheckScriptSuccess(lines) enddef def Test_assignment_with_operator() var lines =<< trim END vim9script class Foo this.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.CheckScriptSuccess(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.CheckScriptSuccess(lines) enddef def Test_expr_after_using_object() var lines =<< trim END vim9script class Something this.label: string = '' endclass def Foo(): Something var v = Something.new() echo 'in Foo(): ' .. typename(v) return v enddef Foo() END v9.CheckScriptSuccess(lines) enddef def Test_class_default_new() var lines =<< trim END vim9script class TextPosition this.lnum: number = 1 this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class Person this.name: string this.age: number = 42 this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class Person this.name: string this.age: number = 42 this.education: string = "unknown" def new(this.name, this.age = v:none, this.education = v:none) enddef endclass var missing = Person.new() END v9.CheckScriptFailure(lines, 'E119:') enddef def Test_class_new_with_object_member() var lines =<< trim END vim9script class C this.str: string this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class C this.str: string this.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.CheckScriptFailure(lines, 'E1013:') lines =<< trim END vim9script class C this.str: string this.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.CheckScriptFailure(lines, 'E1013:') enddef def Test_class_object_member_inits() var lines =<< trim END vim9script class TextPosition this.lnum: number this.col = 1 this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class TextPosition this.lnum this.col = 1 endclass END v9.CheckScriptFailure(lines, 'E1022:') lines =<< trim END vim9script class TextPosition this.lnum = v:none this.col = 1 endclass END v9.CheckScriptFailure(lines, 'E1330:') # Test for initializing an object member with an unknown variable/type lines =<< trim END vim9script class A this.value = init_val endclass END v9.CheckScriptFailureList(lines, ['E121:', 'E1329:']) enddef def Test_class_object_member_access() var lines =<< trim END vim9script class Triple this._one = 1 this.two = 2 public this.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') assert_fails('trip._one = 11', 'E1333') assert_fails('trip.two = 22', 'E1335') trip.three = 33 assert_equal(33, trip.three) assert_fails('trip.four = 4', 'E1334') END v9.CheckScriptSuccess(lines) # Test for a public member variable name beginning with an underscore lines =<< trim END vim9script class A public this._val = 10 endclass END v9.CheckScriptFailure(lines, 'E1332:') lines =<< trim END vim9script class MyCar this.make: string this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class MyCar this.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.CheckScriptFailure(lines, 'E1041:') lines =<< trim END vim9script class Foo this.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.CheckScriptSuccess(lines) # Test for "public" cannot be abbreviated lines =<< trim END vim9script class Something pub this.val = 1 endclass END v9.CheckScriptFailure(lines, 'E1065:') # Test for "public" keyword must be followed by "this" or "static". lines =<< trim END vim9script class Something public val = 1 endclass END v9.CheckScriptFailure(lines, 'E1331:') # Test for "static" cannot be abbreviated lines =<< trim END vim9script class Something stat this.val = 1 endclass END v9.CheckScriptFailure(lines, 'E1065:') enddef def Test_class_object_compare() var class_lines =<< trim END vim9script class Item this.nr = 0 this.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.CheckScriptSuccess(class_lines + test_lines) v9.CheckScriptSuccess( 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.CheckScriptFailure(class_lines + op_lines, 'E1153: Invalid operation for object') v9.CheckScriptFailure(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 this.one = 1 endclass class Two this.two = 2 endclass class TwoMore extends Two this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class One this.one = 1 endclass class Two this.two = 2 endclass var o: One = Two.new() END v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected object<One> but got object<Two>') lines =<< trim END vim9script interface One def GetMember(): number endinterface class Two implements One this.one = 1 def GetMember(): number return this.one enddef endclass var o: One = Two.new(5) assert_equal(5, o.GetMember()) END v9.CheckScriptSuccess(lines) lines =<< trim END vim9script class Num this.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.CheckScriptSuccess(lines) enddef def Test_class_member() # check access rules var lines =<< trim END vim9script class TextPos this.lnum = 1 this.col = 1 static counter = 0 static _secret = 7 public static 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', 'E1338:') def GetCounter(): number return TextPos.counter enddef assert_equal(3, GetCounter()) assert_fails('TextPos.noSuchMember = 2', 'E1337:') assert_fails('TextPos.counter = 5', 'E1335:') assert_fails('TextPos.counter += 5', 'E1335:') assert_fails('echo TextPos._secret', 'E1333:') assert_fails('TextPos._secret = 8', 'E1333:') assert_equal(42, TextPos.anybody) TextPos.anybody = 12 assert_equal(12, TextPos.anybody) TextPos.anybody += 5 assert_equal(17, TextPos.anybody) END v9.CheckScriptSuccess(lines) # example in the help lines =<< trim END vim9script class OtherThing this.size: number static 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.CheckScriptSuccess(lines) # using static class member twice lines =<< trim END vim9script class HTML static 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.CheckScriptSuccess(lines) # access private member in lambda lines =<< trim END vim9script class Foo this._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.CheckScriptSuccess(lines) # access private member in lambda body lines =<< trim END vim9script class Foo this._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.CheckScriptSuccess(lines) # check shadowing lines =<< trim END vim9script class Some static count = 0 def Method(count: number) echo count enddef endclass var s = Some.new() s.Method(7) END v9.CheckScriptFailure(lines, 'E1340: Argument already declared in the class: count') lines =<< trim END vim9script class Some static count = 0 def Method(arg: number) var count = 3 echo arg count enddef endclass var s = Some.new() s.Method(7) END v9.CheckScriptFailure(lines, 'E1341: Variable already declared in the class: count') # Test for using an invalid type for a member variable lines =<< trim END vim9script class A this.val: xxx endclass END v9.CheckScriptFailure(lines, 'E1010:') # Test for no space before or after the '=' when initializing a member # variable lines =<< trim END vim9script class A this.val: number= 10 endclass END v9.CheckScriptFailure(lines, 'E1004:') lines =<< trim END vim9script class A this.val: number =10 endclass END v9.CheckScriptFailure(lines, 'E1004:') # Access a non-existing member lines =<< trim END vim9script class A endclass var a = A.new() var v = a.bar END v9.CheckScriptFailure(lines, 'E1326:') enddef func Test_class_garbagecollect() let lines =<< trim END vim9script class Point this.p = [2, 3] static pl = ['a', 'b'] static pd = {a: 'a', b: 'b'} endclass echo Point.pl Point.pd call test_garbagecollect_now() echo Point.pl Point.pd END call v9.CheckScriptSuccess(lines) let lines =<< trim END vim9script interface View endinterface class Widget this.view: View endclass class MyView implements View this.widget: Widget def new() # this will result in a circular reference to this object this.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.CheckScriptSuccess(lines) endfunc def Test_class_function() var lines =<< trim END vim9script class Value this.value = 0 static 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.CheckScriptSuccess(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.CheckScriptFailure(lines, 'E1318:') 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.CheckScriptFailure(lines, 'E1012: Type mismatch; expected string but got number') lines =<< trim END vim9script class C static def Fc(): number return 'x' enddef endclass defcompile C.Fc END v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected number but got string') # Trying to compile a function using a non-existing class variable lines =<< trim END vim9script defcompile x.Foo() END v9.CheckScriptFailure(lines, 'E475:') # 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.CheckScriptFailure(lines, 'E475:') # Trying to compile a function without specifying the name lines =<< trim END vim9script class A endclass defcompile A. END v9.CheckScriptFailure(lines, 'E475:') # 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.CheckScriptFailureList(lines, ['E1334:', 'E475:']) enddef def Test_class_object_to_string() var lines =<< trim END vim9script class TextPosition this.lnum = 1 this.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.CheckScriptSuccess(lines) enddef def Test_interface_basics() var lines =<< trim END vim9script interface Something this.value: string static count: number def GetCount(): number endinterface END v9.CheckScriptSuccess(lines) lines =<< trim END interface SomethingWrong static count = 7 endinterface END v9.CheckScriptFailure(lines, 'E1342:') lines =<< trim END vim9script interface Some static count: number def Method(count: number) endinterface END v9.CheckScriptFailure(lines, 'E1340: Argument already declared in the class: count', 5) lines =<< trim END vim9script interface Some this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script interface somethingWrong static count = 7 endinterface END v9.CheckScriptFailure(lines, 'E1343: Interface name must start with an uppercase letter: somethingWrong') lines =<< trim END vim9script interface SomethingWrong this.value: string static count = 7 def GetCount(): number endinterface END v9.CheckScriptFailure(lines, 'E1344:') lines =<< trim END vim9script interface SomethingWrong this.value: string static count: number def GetCount(): number return 5 enddef endinterface END v9.CheckScriptFailure(lines, 'E1345: Not a valid command in an interface: return 5') 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 def Test_class_implements_interface() var lines =<< trim END vim9script interface Some static count: number def Method(nr: number) endinterface class SomeImpl implements Some static count: number def Method(nr: number) echo nr enddef endclass interface Another this.member: string endinterface class AnotherImpl implements Some, Another this.member = 'abc' static count: number def Method(nr: number) echo nr enddef endclass END v9.CheckScriptSuccess(lines) lines =<< trim END vim9script interface Some static counter: number endinterface class SomeImpl implements Some implements Some static count: number endclass END v9.CheckScriptFailure(lines, 'E1350:') lines =<< trim END vim9script interface Some static counter: number endinterface class SomeImpl implements Some, Some static count: number endclass END v9.CheckScriptFailure(lines, 'E1351: Duplicate interface after "implements": Some') lines =<< trim END vim9script interface Some static counter: number def Method(nr: number) endinterface class SomeImpl implements Some static count: number def Method(nr: number) echo nr enddef endclass END v9.CheckScriptFailure(lines, 'E1348: Member "counter" of interface "Some" not implemented') lines =<< trim END vim9script interface Some static count: number def Methods(nr: number) endinterface class SomeImpl implements Some static count: number def Method(nr: number) echo nr enddef endclass END v9.CheckScriptFailure(lines, 'E1349: Function "Methods" of interface "Some" not implemented') # Check different order of members in class and interface works. lines =<< trim END vim9script interface Result public this.label: string this.errpos: number endinterface # order of members is opposite of interface class Failure implements Result this.errpos: number = 42 public this.label: string = 'label' endclass def Test() var result: Result = Failure.new() assert_equal('label', result.label) assert_equal(42, result.errpos) result.label = 'different' assert_equal('different', result.label) assert_equal(42, result.errpos) enddef Test() END v9.CheckScriptSuccess(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.CheckScriptFailure(lines, 'E1315:') # Trailing characters after a class name lines =<< trim END vim9script class A bbb endclass END v9.CheckScriptFailure(lines, 'E488:') # using "implements" with a non-existing class lines =<< trim END vim9script class A implements B endclass END v9.CheckScriptFailure(lines, 'E1346:') # using "implements" with a regular class lines =<< trim END vim9script class A endclass class B implements A endclass END v9.CheckScriptFailure(lines, 'E1347:') # using "implements" with a variable lines =<< trim END vim9script var T: number = 10 class A implements T endclass END v9.CheckScriptFailure(lines, 'E1347:') # all the class methods in an "interface" should be implemented lines =<< trim END vim9script interface A static def Foo() endinterface class B implements A endclass END v9.CheckScriptFailure(lines, 'E1349:') 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.CheckScriptSuccess(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.CheckScriptSuccess(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.CheckScriptSuccess(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.CheckScriptSuccess(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.CheckScriptSuccess(lines) enddef def Test_class_used_as_type() var lines =<< trim END vim9script class Point this.x = 0 this.y = 0 endclass var p: Point p = Point.new(2, 33) assert_equal(2, p.x) assert_equal(33, p.y) END v9.CheckScriptSuccess(lines) lines =<< trim END vim9script interface HasX this.x: number endinterface class Point implements HasX this.x = 0 this.y = 0 endclass var p: Point p = Point.new(2, 33) var hx = p assert_equal(2, hx.x) END v9.CheckScriptSuccess(lines) lines =<< trim END vim9script class Point this.x = 0 this.y = 0 endclass var p: Point p = 'text' END v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected object<Point> but got string') enddef def Test_class_extends() var lines =<< trim END vim9script class Base this.one = 1 def GetOne(): number return this.one enddef endclass class Child extends Base this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class Base this.one = 1 endclass class Child extends Base this.two = 2 endclass var o = Child.new(3, 44) assert_equal(3, o.one) assert_equal(44, o.two) END v9.CheckScriptSuccess(lines) lines =<< trim END vim9script class Base this.one = 1 endclass class Child extends Base extends Base this.two = 2 endclass END v9.CheckScriptFailure(lines, 'E1352: Duplicate "extends"') lines =<< trim END vim9script class Child extends BaseClass this.two = 2 endclass END v9.CheckScriptFailure(lines, 'E1353: Class name not found: BaseClass') lines =<< trim END vim9script var SomeVar = 99 class Child extends SomeVar this.two = 2 endclass END v9.CheckScriptFailure(lines, 'E1354: Cannot extend SomeVar') lines =<< trim END vim9script class Base this.name: string def ToString(): string return this.name enddef endclass class Child extends Base this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class Child this.age: number def ToString(): number return this.age enddef def ToString(): string return this.age enddef endclass END v9.CheckScriptFailure(lines, 'E1355: Duplicate function: ToString') lines =<< trim END vim9script class Child this.age: number def ToString(): string return super .ToString() .. ': ' .. this.age enddef endclass var o = Child.new(42) echo o.ToString() END v9.CheckScriptFailure(lines, 'E1356:') lines =<< trim END vim9script class Base this.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.CheckScriptFailure(lines, 'E1357:') lines =<< trim END vim9script class Child this.age: number def ToString(): string return super.ToString() .. ': ' .. this.age enddef endclass var o = Child.new(42) echo o.ToString() END v9.CheckScriptFailure(lines, 'E1358:') lines =<< trim END vim9script class Base this.name: string static def ToString(): string return 'Base class' enddef endclass class Child extends Base this.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.CheckScriptSuccess(lines) lines =<< trim END vim9script class Base this.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.CheckScriptFailure(lines, 'E1325: Method not found on class "Child": new(') # base class with more than one object member lines =<< trim END vim9script class Result this.success: bool this.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.CheckScriptSuccess(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.CheckScriptFailure(lines, 'E1315:') 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.CheckScriptSuccess(lines) unlet g:result # Using super, Child invokes Base method which has optional arg. #12471 lines =<< trim END vim9script class Base this.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.CheckScriptSuccess(lines) enddef def Test_class_import() var lines =<< trim END vim9script export class Animal this.kind: string this.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 def Test_abstract_class() var lines =<< trim END vim9script abstract class Base this.name: string endclass class Person extends Base this.age: number endclass var p: Base = Person.new('Peter', 42) assert_equal('Peter', p.name) assert_equal(42, p.age) END v9.CheckScriptSuccess(lines) lines =<< trim END vim9script abstract class Base this.name: string endclass class Person extends Base this.age: number endclass var p = Base.new('Peter') END v9.CheckScriptFailure(lines, 'E1325: Method not found on class "Base": new(') lines =<< trim END abstract class Base this.name: string endclass END v9.CheckScriptFailure(lines, 'E1316:') # Abstract class cannot have a "new" function lines =<< trim END vim9script abstract class Base def new() enddef endclass END v9.CheckScriptFailure(lines, 'E1359:') enddef def Test_closure_in_class() var lines =<< trim END vim9script class Foo this.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.CheckScriptSuccess(lines) enddef def Test_call_constructor_from_legacy() var lines =<< trim END vim9script var newCalled = 'false' class A def new() newCalled = 'true' enddef endclass export def F(options = {}): any return A enddef g:p = F() legacy call p.new() assert_equal('true', newCalled) END v9.CheckScriptSuccess(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.CheckScriptSuccess(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.CheckScriptSuccess(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 this.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 this.value: 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.CheckScriptSuccess(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.CheckScriptSuccess(lines) 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 this._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.CheckScriptSuccess(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.CheckScriptSuccess(lines) enddef " Test for using members from three levels of classes def Test_multi_level_member_access() var lines =<< trim END vim9script class A this.val1: number = 0 this.val2: number = 0 this.val3: number = 0 endclass class B extends A this.val2: number = 0 this.val3: number = 0 endclass class C extends B this.val3: number = 0 endclass def A_members(a: A) a.val1 += 1 a.val2 += 1 a.val3 += 1 enddef def B_members(b: B) b.val1 += 1 b.val2 += 1 b.val3 += 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(3, cobj.val2) assert_equal(3, cobj.val3) END v9.CheckScriptSuccess(lines) enddef " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker