Skip to content

[Bug]: A multi-return of ((a,b)|unknown) from an unannotated recursive function fails to spread into an argument list #82

Description

@AmyJeanes

Area: glua_check (expected to affect the language server too; the repro and diagnostics below are from glua_check)


Summary

A function whose return type resolves to ((1,2)|unknown) cannot be spread into another call's argument list. The unknown arm comes from an unannotated recursive function: the return type does not reach a fixpoint (the base case is return 1, 2), so it is inferred as ((1,2)|unknown). Spreading that into a two-parameter call produces two warnings - the first value types as the whole union, and the second parameter is reported missing, as if the arity might be 1.

Both are warnings, so this fails glua_check --warnings-as-errors with exit 1.

This is adjacent to the now-fixed #47, not a regression of it: #47's own cases - a plain multi-return spread, and the pcall form - are clean on 1.2.0. The surviving trigger here is specifically the unknown-tainted union from the unannotated recursion.

Minimal repro

Repro: bug3-recursive-multiret-spread.zip

Standalone - no workspace.library and no GLua annotations. .luarc.json is only {"runtime":{"version":"LuaJIT"}}.

---@param x number
---@param y number
local function takesTwo(x, y) end

-- unannotated recursive fn -> return infers as ((1,2)|unknown)
local function r2(n)
    if n and n > 0 then return r2(n - 1) end
    return 1, 2
end

takesTwo(r2(5))

glua_check --warnings-as-errors . on the repro reports exactly (line 28 is the takesTwo(r2(5)) line in repro.lua, below the header comment):

warning: expected `number` but found `((1,2)|unknown)`.  [param-type-mismatch]
  --> repro.lua:28:10
warning: expected 2 parameters but found 1. missing parameter: y [missing-parameter]
  --> repro.lua:28:15

r2 always returns two numbers - the recursive arm returns r2's own result, and the base arm returns 1, 2. A fixpoint on the self-reference would give (number, number); instead the recursive arm is left as unknown and taints the union.

Two controls that isolate it

Both are in the repro file, and both are clean:

Real-world impact

TARDIS sh_screen_ui.lua lays out screen buttons with a recursive movebutton(button) that recurses (return movebutton(button)) and otherwise returns w, h; v:SetPos(movebutton(v)) spreads it. On 1.2.0 that fired the two warnings above and failed our --warnings-as-errors build. We added ---@return number, number (which it genuinely is) to clear it.

Low severity in practice - the fix is an annotation you would want anyway - but the inference producing unknown for a self-recursive multi-return, and that unknown then breaking an otherwise-valid spread, looked worth flagging while the multi-return area (#47) is fresh.

Expected

r2's return resolves to (number, number) via the base case, and takesTwo(r2(5)) spreads cleanly - matching the non-recursive and annotated forms, which already are clean.

Environment

  • glua_check 1.2.0
  • annotations gluals-annotations-prerelease @ aae7341 (not needed for the repro)
  • Windows 11

Disclosure: this report was researched and written by Claude Code working in my repositories. Every claim in it was reproduced against a real workspace on 1.2.0 rather than asserted, and the two controls plus a re-run of #47's own cases were checked before filing. The r2 / takesTwo names are a reduction of the real movebutton layout function; the structure (recursive multi-return spread into a call) is the same.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions