patch 9.1.0673: Vim9: too recursive func calls when calling super-class method
Problem: Vim9: too recursive func calls when calling super-class method
with non-overriden super-call methods. (Aliaksei Budavei)
Solution: use interface method, when super is to be used (Ernie Rael)
When compiling "super.Func()" force class context to class that defines
function that is doing "super.Func()".
ISN_METHODCALL arg "cmf_is_super" for specific ufunc.
fixes: #15448
fixes: #15463 (2) super.method may not execute in context of defining
class
closes: #15477
Signed-off-by: Ernie Rael <errael@raelity.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro
index 1b2f79c..8ee33b8 100644
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -58,7 +58,7 @@
int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call);
int generate_LISTAPPEND(cctx_T *cctx);
int generate_BLOBAPPEND(cctx_T *cctx);
-int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, class_T *cl, int mi, int pushed_argcount);
+int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, class_T *cl, int mi, int pushed_argcount, int is_super);
int generate_UCALL(cctx_T *cctx, char_u *name, int argcount);
int check_func_args_from_type(cctx_T *cctx, type_T *type, int argcount, int at_top, char_u *name);
int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top);
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index 56c7b99..8791a52 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -3252,6 +3252,141 @@
v9.CheckSourceSuccess(lines)
enddef
+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)
+enddef
+
def Test_class_import()
var lines =<< trim END
vim9script
diff --git a/src/version.c b/src/version.c
index 159357e..edde65f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 673,
+/**/
672,
/**/
671,
diff --git a/src/vim9.h b/src/vim9.h
index 54938fe..c502ac4 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -238,8 +238,9 @@
// arguments to ISN_METHODCALL
typedef struct {
class_T *cmf_itf; // interface used
- int cmf_idx; // index in "def_functions" for ISN_DCALL
+ int cmf_idx; // index in "def_functions" for ISN_METHODCALL
int cmf_argcount; // number of arguments on top of stack
+ int cmf_is_super; // doing "super.Func", use cmf_itf, not cmf_idx
} cmfunc_T;
// arguments to ISN_PCALL
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 40b5499..f523e27 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -4400,14 +4400,23 @@
goto on_error;
}
- class_T *cl = obj->obj_class;
+ ufunc_T *ufunc;
+ if (mfunc->cmf_is_super)
+ // Doing "super.Func", use the specific ufunc.
+ ufunc = mfunc->cmf_itf->class_obj_methods[
+ mfunc->cmf_idx];
+ else
+ {
+ class_T *cl = obj->obj_class;
- // convert the interface index to the object index
- int idx = object_index_from_itf_index(mfunc->cmf_itf,
- TRUE, mfunc->cmf_idx, cl);
+ // convert the interface index to the object index
+ int idx = object_index_from_itf_index(mfunc->cmf_itf,
+ TRUE, mfunc->cmf_idx, cl);
+ ufunc = cl->class_obj_methods[idx];
+ }
- if (call_ufunc(cl->class_obj_methods[idx], NULL,
- mfunc->cmf_argcount, ectx, NULL, NULL) == FAIL)
+ if (call_ufunc(ufunc, NULL, mfunc->cmf_argcount, ectx,
+ NULL, NULL) == FAIL)
goto on_error;
}
break;
diff --git a/src/vim9expr.c b/src/vim9expr.c
index c2428f4..cfdea7b 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -349,6 +349,11 @@
else
{
// type->tt_type == VAR_OBJECT: method call
+ // When compiling Func and doing "super.SomeFunc()", must be in the
+ // class context that defines Func.
+ if (is_super)
+ cl = cctx->ctx_ufunc->uf_defclass;
+
function_count = cl->class_obj_method_count;
child_count = cl->class_obj_method_count_child;
functions = cl->class_obj_methods;
@@ -419,8 +424,8 @@
return generate_PCALL(cctx, argcount, name, ocm->ocm_type, TRUE);
if (type->tt_type == VAR_OBJECT
&& (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED)))
- return generate_CALL(cctx, ufunc, cl, fi, argcount);
- return generate_CALL(cctx, ufunc, NULL, 0, argcount);
+ return generate_CALL(cctx, ufunc, cl, fi, argcount, is_super);
+ return generate_CALL(cctx, ufunc, NULL, 0, argcount, FALSE);
}
if (type->tt_type == VAR_OBJECT)
@@ -1035,7 +1040,7 @@
ufunc_T *uf = class_get_builtin_method(type->tt_class, builtin_method,
&method_idx);
if (uf != NULL)
- res = generate_CALL(cctx, uf, type->tt_class, method_idx, 0);
+ res = generate_CALL(cctx, uf, type->tt_class, method_idx, 0, FALSE);
}
return res;
@@ -1238,7 +1243,7 @@
{
if (!func_is_global(ufunc))
{
- res = generate_CALL(cctx, ufunc, NULL, 0, argcount);
+ res = generate_CALL(cctx, ufunc, NULL, 0, argcount, FALSE);
goto theend;
}
if (!has_g_namespace
@@ -1257,7 +1262,7 @@
if (cctx->ctx_ufunc->uf_defclass == cl)
{
res = generate_CALL(cctx, cl->class_class_functions[mi], NULL,
- 0, argcount);
+ 0, argcount, FALSE);
}
else
{
@@ -1285,7 +1290,7 @@
// If we can find a global function by name generate the right call.
if (ufunc != NULL)
{
- res = generate_CALL(cctx, ufunc, NULL, 0, argcount);
+ res = generate_CALL(cctx, ufunc, NULL, 0, argcount, FALSE);
goto theend;
}
diff --git a/src/vim9instr.c b/src/vim9instr.c
index ad8beb1..4368e3c 100644
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -1805,6 +1805,8 @@
* Generate an ISN_DCALL, ISN_UCALL or ISN_METHODCALL instruction.
* When calling a method on an object, of which we know the interface only,
* then "cl" is the interface and "mi" the method index on the interface.
+ * save is_super in the "isn->isn_arg"; it flags execution to use mfunc
+ * directly to determine ufunc.
* Return FAIL if the number of arguments is wrong.
*/
int
@@ -1813,7 +1815,8 @@
ufunc_T *ufunc,
class_T *cl,
int mi,
- int pushed_argcount)
+ int pushed_argcount,
+ int is_super)
{
isn_T *isn;
int regular_args = ufunc->uf_args.ga_len;
@@ -1898,6 +1901,7 @@
++cl->class_refcount;
isn->isn_arg.mfunc->cmf_idx = mi;
isn->isn_arg.mfunc->cmf_argcount = argcount;
+ isn->isn_arg.mfunc->cmf_is_super = is_super;
}
else if (isn->isn_type == ISN_DCALL)
{