From 821c9d79408ecb1eeed9ac2a37e8d741f53a5c6c Mon Sep 17 00:00:00 2001 From: romanspector Date: Thu, 30 Jul 2026 23:07:00 +0300 Subject: [PATCH 1/2] Infer pcall's own return when the called function has none MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `getReturn` always yields a node — an empty one when there is nothing to take — so `if node then` passed even for a function declared as `fun()`. That empty node became the result and the type stayed unknown, although `pcall` declares its second result as `any`: on failure it carries the error value. An empty node is no longer accepted as an answer; compilation falls through to `pcall`'s own declaration instead. The same applies to `xpcall`, where the message handler's result takes the place of the missing return. Functions that do return a value are unaffected, as the existing tests cover. Co-Authored-By: Claude Opus 5 --- script/vm/compiler.lua | 16 ++++++++++------ test/type_inference/common.lua | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index 5a673e5fa..4063d70db 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -2191,12 +2191,14 @@ local compilerSwitch = util.switch() newArgs[#newArgs+1] = args[i] end local node = getReturn(args[1], index - 1, newArgs) - if node then + if node and not node:isEmpty() then vm.setNode(source, node) + return end - return - end - if func.special == 'xpcall' and index > 1 then + -- The called function has no such return, but `pcall` still does: on failure + -- the second result is the error value. Fall through to its own declaration, + -- otherwise the result stays unknown. + elseif func.special == 'xpcall' and index > 1 then if not args then return end @@ -2205,10 +2207,12 @@ local compilerSwitch = util.switch() newArgs[#newArgs+1] = args[i] end local node = getReturn(args[1], index - 1, newArgs) - if node then + if node and not node:isEmpty() then vm.setNode(source, node) + return end - return + -- Same as `pcall`: the message handler's result comes in place of the missing + -- return, and its type is declared on `xpcall` itself. end if func.special == 'require' then if index == 2 then diff --git a/test/type_inference/common.lua b/test/type_inference/common.lua index bd2de1ea5..5db2e98f3 100644 --- a/test/type_inference/common.lua +++ b/test/type_inference/common.lua @@ -320,6 +320,20 @@ end _, = xpcall(x) ]] +-- A declared function without returns leaves nothing to take the second result from, but +-- `pcall` still has one of its own: the error value. +TEST 'any' [[ +---@type fun() +local x +_, = pcall(x) +]] + +TEST 'any' [[ +---@type fun() +local x +_, = xpcall(x, debug.traceback) +]] + TEST 'A' [[ ---@class A From 6ea0c4a13fcd4b530c613670aa116a2f754ac379 Mon Sep 17 00:00:00 2001 From: romanspector Date: Fri, 31 Jul 2026 11:17:25 +0300 Subject: [PATCH 2/2] Add changelog entry --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index c9375a4cd..6972098b7 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ## Unreleased +* `FIX` Infer `pcall`/`xpcall` second result from their own declaration when the called function returns nothing [#3440](https://github.com/LuaLS/lua-language-server/pull/3440) * `NEW` Support type inference for `@field` and `@type` function declarations in method overrides [#3367](https://github.com/LuaLS/lua-language-server/issues/3367) * `FIX` Deduplicate documentation bindings for parameters * `FIX` Correct `math.type` meta return annotation to use `nil` instead of the string literal `'nil'`