From 39b77f64fb22c8174d4c85a97eb9efb56739985c Mon Sep 17 00:00:00 2001 From: Yura Lazarev Date: Mon, 13 Jul 2026 15:21:51 +0200 Subject: [PATCH] fix: fromStringImpl is the 4-ary function its Fn4 declaration requires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The entry was a chain of nested single-argument closures, but runFn4 calls the underlying implementation n-ary — fn(a, b, c, d) — and Lua silently drops the surplus arguments on a 1-ary function, so Data.Number.fromString returned the inner closure instead of a Maybe Number on every PureScript call path (the runtime runFn4 fallback and the compiler uncurried lift alike). Flatten the implementation to a single 4-ary function and pin the n-ary convention in the regression guard. Refs purescript-lua/purescript-lua#186. --- changelog.d/20260713_144500_fromstring_fn4.md | 7 ++++++ src/Data/Number.lua | 25 ++++++++----------- test/regression/number.lua | 6 +++-- 3 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 changelog.d/20260713_144500_fromstring_fn4.md diff --git a/changelog.d/20260713_144500_fromstring_fn4.md b/changelog.d/20260713_144500_fromstring_fn4.md new file mode 100644 index 0000000..0e5235a --- /dev/null +++ b/changelog.d/20260713_144500_fromstring_fn4.md @@ -0,0 +1,7 @@ +### Fixed + +- `fromStringImpl` matches its `Fn4` declaration: the Lua entry is a single + 4-ary function (previously it was a chain of nested single-argument + closures, so `Data.Number.fromString` returned a function instead of a + `Maybe Number` — Lua silently drops the surplus arguments of the n-ary + `runFn4` call). diff --git a/src/Data/Number.lua b/src/Data/Number.lua index 5242d3c..fc922ff 100644 --- a/src/Data/Number.lua +++ b/src/Data/Number.lua @@ -42,20 +42,15 @@ return { sqrt = (math.sqrt), tan = (math.tan), trunc = (function(x) return x < 0 and math.ceil(x) or math.floor(x) end), - fromStringImpl = (function(str) - return function(isFinite) - return function(just) - return function(nothing) - -- Mirror JS parseFloat: a leading-numeric prefix is parsed and - -- trailing junk (and a whitespace prefix) is tolerated. Guard against - -- a failed parse — the old code wrapped tonumber's nil in Just — and - -- use the supplied isFinite predicate so non-finite results are Nothing. - local prefix = str:match("^%s*([%-+]?%d*%.?%d+[eE]?[%-+]?%d*)") - local x = prefix and tonumber(prefix) or nil - if x ~= nil and isFinite(x) then return just(x) end - return nothing - end - end - end + fromStringImpl = (function(str, isFinite, just, nothing) + -- Declared Fn4, so the entry is a single 4-ary function. + -- Mirror JS parseFloat: a leading-numeric prefix is parsed and + -- trailing junk (and a whitespace prefix) is tolerated. Guard against + -- a failed parse — the old code wrapped tonumber's nil in Just — and + -- use the supplied isFinite predicate so non-finite results are Nothing. + local prefix = str:match("^%s*([%-+]?%d*%.?%d+[eE]?[%-+]?%d*)") + local x = prefix and tonumber(prefix) or nil + if x ~= nil and isFinite(x) then return just(x) end + return nothing end) } diff --git a/test/regression/number.lua b/test/regression/number.lua index d118ca9..9a6d688 100644 --- a/test/regression/number.lua +++ b/test/regression/number.lua @@ -50,10 +50,12 @@ check("min 1 NaN is NaN", isNaN(M.min(1)(nan)), tostring(M.min(1)(nan))) check("min NaN 1 is NaN", isNaN(M.min(nan)(1)), tostring(M.min(nan)(1))) check("min 3 7 == 3", M.min(3)(7) == 3, tostring(M.min(3)(7))) --- #93 fromString (via the Fn4-style curried FFI) +-- #93 fromString. fromStringImpl is declared `Fn4`, so the Lua entry is a +-- 4-ary function called with all arguments at once (`runFn4` and the +-- compiler's uncurried lift both call it that way). local function just(x) return {tag = "just", value = x} end local nothing = {tag = "nothing"} -local function fromString(s) return M.fromStringImpl(s)(M.isFinite)(just)(nothing) end +local function fromString(s) return M.fromStringImpl(s, M.isFinite, just, nothing) end local function isJust(m, v) return type(m) == "table" and m.tag == "just" and m.value == v end local function isNothing(m) return type(m) == "table" and m.tag == "nothing" end