[PATCH v4 0/1] MR10362: vbscript: Support chained call syntax like dict.Keys()(i).
Treat '(' after tEMPTYBRACKETS as an argument list opener in the lexer, so that expressions like GetArr()(0) or dict.Keys()(i) parse correctly. Also dereference VT_BYREF|VT_VARIANT results in vcall so chained indexing on call results works at runtime. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58051 This includes some of the work done in !10308, preferably that one is merged first. -- v4: vbscript: Support chained call syntax like dict.Keys()(i). https://gitlab.winehq.org/wine/wine/-/merge_requests/10362
From: Francis De Brabandere <francisdb@gmail.com> Treat '(' after tEMPTYBRACKETS as an argument list opener in the lexer, so that expressions like GetArr()(0) or dict.Keys()(i) parse correctly. Also dereference VT_BYREF|VT_VARIANT results in vcall so chained indexing on call results works at runtime. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58051 --- dlls/vbscript/interp.c | 9 ++++++++- dlls/vbscript/lex.c | 3 ++- dlls/vbscript/tests/lang.vbs | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/dlls/vbscript/interp.c b/dlls/vbscript/interp.c index 779f3f881a5..8389c6b9704 100644 --- a/dlls/vbscript/interp.c +++ b/dlls/vbscript/interp.c @@ -708,13 +708,20 @@ static HRESULT interp_icallv(exec_ctx_t *ctx) static HRESULT interp_vcall(exec_ctx_t *ctx) { const unsigned arg_cnt = ctx->instr->arg1.uint; - VARIANT res, *v; + VARIANT res = {0}, *v; HRESULT hres; TRACE("\n"); v = stack_pop(ctx); hres = variant_call(ctx, v, arg_cnt, &res); + if(SUCCEEDED(hres) && V_VT(&res) == (VT_BYREF|VT_VARIANT)) { + VARIANT tmp; + V_VT(&tmp) = VT_EMPTY; + hres = VariantCopyInd(&tmp, &res); + if(SUCCEEDED(hres)) + res = tmp; + } VariantClear(v); if(FAILED(hres)) return hres; diff --git a/dlls/vbscript/lex.c b/dlls/vbscript/lex.c index 75838ac586a..d595bda7fa0 100644 --- a/dlls/vbscript/lex.c +++ b/dlls/vbscript/lex.c @@ -465,7 +465,8 @@ static int parse_next_token(void *lval, unsigned *loc, parser_ctx_t *ctx) * Parser can't predict if bracket is part of argument expression or an argument * in call expression. We predict it here instead. */ - if(ctx->last_token == tIdentifier || ctx->last_token == ')' || ctx->last_token == tME) + if(ctx->last_token == tIdentifier || ctx->last_token == ')' || ctx->last_token == tME + || ctx->last_token == tEMPTYBRACKETS) return '('; return tEXPRLBRACKET; case '"': diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index 4374d7b1f25..40ebe7f23fd 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -1659,6 +1659,26 @@ ok arr2(1,2) = 2, "arr2(1,2) = " & arr2(1,2) x = Array(Array(3)) call ok(x(0)(0) = 3, "x(0)(0) = " & x(0)(0)) +Function GetABC() + GetABC = Array("a", "b", "c") +End Function +call ok(GetABC()(0) = "a", "GetABC()(0) = " & GetABC()(0)) +call ok(GetABC()(1) = "b", "GetABC()(1) = " & GetABC()(1)) +call ok(GetABC()(2) = "c", "GetABC()(2) = " & GetABC()(2)) + +Function GetNested() + GetNested = Array(Array(1, 2), Array(3, 4)) +End Function +call ok(GetNested()(0)(0) = 1, "GetNested()(0)(0) = " & GetNested()(0)(0)) +call ok(GetNested()(0)(1) = 2, "GetNested()(0)(1) = " & GetNested()(0)(1)) +call ok(GetNested()(1)(0) = 3, "GetNested()(1)(0) = " & GetNested()(1)(0)) +call ok(GetNested()(1)(1) = 4, "GetNested()(1)(1) = " & GetNested()(1)(1)) + +x = GetABC()(0) & GetABC()(2) +call ok(x = "ac", "GetABC()(0) & GetABC()(2) = " & x) + +call ok(Array(10,20,30)(1) = 20, "Array(10,20,30)(1) = " & Array(10,20,30)(1)) + function seta0(arr) arr(0) = 2 seta0 = 1 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10362
On Mon Mar 23 23:45:18 2026 +0000, Jacek Caban wrote:
While it seems that we should indeed do that in this case, there are more cases with the same problem. Could we do that in `variant_call`, or even in `disp_call`, instead? Added some more test cases.
Moving the fix to variant_call doesn't work because variant_call's VT_BYREF|VT_VARIANT return is correct and needed for the do_icall path — tests like getVT(arr(1)) expect "VT_EMPTY*" (a byref), which matches Windows behavior. Changing variant_call to always copy breaks these tests. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10362#note_133563
On Tue Mar 24 19:26:15 2026 +0000, Francis De Brabandere wrote:
Added some more test cases. Moving the fix to variant_call doesn't work because variant_call's VT_BYREF|VT_VARIANT return is correct and needed for the do_icall path — tests like getVT(arr(1)) expect "VT_EMPTY*" (a byref), which matches Windows behavior. Changing variant_call to always copy breaks these tests. Not sure if it's up to me to mark this resolved or if I should wait for you to mark it resolved?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10362#note_133565
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)