From 9003c17092a9fd232827c40929cccdf7e421dd4d Mon Sep 17 00:00:00 2001 From: Andrew Condon Date: Sat, 10 Jan 2026 15:29:30 +0100 Subject: [PATCH 1/2] fix: wrap table literals in parentheses before indexing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Lua, table constructor literals cannot be directly indexed without parentheses. The expression `{ ["key"] = val }["key"]` is a syntax error, while `({ ["key"] = val })["key"]` is valid. This fix adds the same `wrapPrec PrecAtom` wrapping to `VarIndex` that was already applied to `VarField`, ensuring table literals are properly parenthesized when used with bracket indexing. Fixes pattern matching on ADT constructors that would generate invalid Lua like: if "Mod∷Type.Ctor" == { ["$ctor"] = "Mod∷Type.Ctor" }["$ctor"] then Now correctly generates: if "Mod∷Type.Ctor" == ({ ["$ctor"] = "Mod∷Type.Ctor" })["$ctor"] then Co-Authored-By: Claude Opus 4.5 --- lib/Language/PureScript/Backend/Lua/Printer.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Language/PureScript/Backend/Lua/Printer.hs b/lib/Language/PureScript/Backend/Lua/Printer.hs index 5e8b419..5025a48 100644 --- a/lib/Language/PureScript/Backend/Lua/Printer.hs +++ b/lib/Language/PureScript/Backend/Lua/Printer.hs @@ -108,7 +108,7 @@ printRow = \case printVar ∷ Lua.Var → ADoc printVar = \case Lua.VarName name → printName name - Lua.VarIndex (Ann e) (Ann i) → printedExp e <> brackets (printedExp i) + Lua.VarIndex (Ann e) (Ann i) → wrapPrec PrecAtom (printExp e) <> brackets (printedExp i) Lua.VarField (Ann e) n → wrapPrec PrecAtom (printExp e) <> "." <> printName n printFunctionCall ∷ PADoc → [PADoc] → ADoc From 772da83757a6bf25ba0fd94e784f03b77750283f Mon Sep 17 00:00:00 2001 From: Yura Lazarev Date: Sun, 14 Jun 2026 10:38:33 +0200 Subject: [PATCH 2/2] test: printer unit test for indexing a table literal Mirror the existing VarField spec case for VarIndex: a table constructor must be parenthesised before bracket indexing, since `{ ["foo"] = 1 }["foo"]` is a Lua syntax error while `({ ["foo"] = 1 })["foo"]` is valid. Without the preceding fix this assertion fails with a clear diff. Regenerate the CharLiterals and StringCodePoints goldens, where the same wrapPrec PrecAtom now parenthesises function-call results before ["$ctor"] indexing, matching VarField. Purely syntactic: IR and eval output are unchanged and both files pass luacheck. --- .../Language/PureScript/Backend/Lua/Printer/Spec.hs | 13 +++++++++++++ test/ps/output/Golden.CharLiterals.Test/golden.lua | 4 ++-- .../output/Golden.StringCodePoints.Test/golden.lua | 8 ++++---- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/test/Language/PureScript/Backend/Lua/Printer/Spec.hs b/test/Language/PureScript/Backend/Lua/Printer/Spec.hs index f1f4857..56c6ef2 100644 --- a/test/Language/PureScript/Backend/Lua/Printer/Spec.hs +++ b/test/Language/PureScript/Backend/Lua/Printer/Spec.hs @@ -35,6 +35,19 @@ spec = do let s = Lua.assign (Lua.VarName [Lua.name|foo|]) (Lua.Boolean True) renderedStatement s `shouldBe` "foo = true" + describe "VarIndex" do + it "var[index]" do + let e = Lua.varName [Lua.name|expr|] + renderedExpression (Lua.varIndex e (Lua.String "foo")) + `shouldBe` "expr[\"foo\"]" + + -- A table constructor must be parenthesised before bracket indexing: + -- `{ ["foo"] = 1 }["foo"]` is a Lua syntax error, `({ … })["foo"]` is not. + it "({[\"foo\"] = 1})[\"foo\"]" do + let e = Lua.table [Lua.tableRowKV (Lua.String "foo") (Lua.Integer 1)] + renderedExpression (Lua.varIndex e (Lua.String "foo")) + `shouldBe` "({ [\"foo\"] = 1 })[\"foo\"]" + describe "Local declaration" do it "without a value" do let s = Lua.Local [Lua.name|foo|] Nothing diff --git a/test/ps/output/Golden.CharLiterals.Test/golden.lua b/test/ps/output/Golden.CharLiterals.Test/golden.lua index 0faefe2..363105b 100644 --- a/test/ps/output/Golden.CharLiterals.Test/golden.lua +++ b/test/ps/output/Golden.CharLiterals.Test/golden.lua @@ -124,7 +124,7 @@ return M.Golden_CharLiterals_Test_discard(M.Effect_Console_foreign.log(M.Golden_ return M.Golden_CharLiterals_Test_discard(M.Effect_Console_foreign.log(M.Golden_CharLiterals_Test_show("a")))(function( ) return M.Golden_CharLiterals_Test_discard(M.Effect_Console_foreign.log(M.Golden_CharLiterals_Test_show1(M.Data_Eq_eqChar.eq("\n")("\n"))))(function( ) return M.Effect_Console_foreign.log(M.Golden_CharLiterals_Test_show1((function( ) - if "Data.Ordering∷Ordering.LT" == ((function() + if "Data.Ordering∷Ordering.LT" == (((function() local unsafeCoerceImpl = function(lt) return function(eq) return function(gt) @@ -147,7 +147,7 @@ return M.Golden_CharLiterals_Test_discard(M.Effect_Console_foreign.log(M.Golden_ ["$ctor"] = "Data.Ordering∷Ordering.LT" })({ ["$ctor"] = "Data.Ordering∷Ordering.EQ" })({ ["$ctor"] = "Data.Ordering∷Ordering.GT" - })("\t")("\n")["$ctor"] then + })("\t")("\n"))["$ctor"] then return true else return false diff --git a/test/ps/output/Golden.StringCodePoints.Test/golden.lua b/test/ps/output/Golden.StringCodePoints.Test/golden.lua index dd273d1..9a0c9ce 100644 --- a/test/ps/output/Golden.StringCodePoints.Test/golden.lua +++ b/test/ps/output/Golden.StringCodePoints.Test/golden.lua @@ -301,7 +301,7 @@ M.Data_Ord_compare = function(dict) return dict.compare end M.Data_Ord_greaterThanOrEq = function(dictOrd) return function(a1) return function(a2) - if "Data.Ordering∷Ordering.LT" == M.Data_Ord_compare(dictOrd)(a1)(a2)["$ctor"] then + if "Data.Ordering∷Ordering.LT" == (M.Data_Ord_compare(dictOrd)(a1)(a2))["$ctor"] then return false else return true @@ -312,7 +312,7 @@ end M.Data_Ord_lessThan = function(dictOrd) return function(a1) return function(a2) - if "Data.Ordering∷Ordering.LT" == M.Data_Ord_compare(dictOrd)(a1)(a2)["$ctor"] then + if "Data.Ordering∷Ordering.LT" == (M.Data_Ord_compare(dictOrd)(a1)(a2))["$ctor"] then return true else return false @@ -323,7 +323,7 @@ end M.Data_Ord_lessThanOrEq = function(dictOrd) return function(a1) return function(a2) - if "Data.Ordering∷Ordering.GT" == M.Data_Ord_compare(dictOrd)(a1)(a2)["$ctor"] then + if "Data.Ordering∷Ordering.GT" == (M.Data_Ord_compare(dictOrd)(a1)(a2))["$ctor"] then return false else return true @@ -496,7 +496,7 @@ M.Data_String_CodePoints_unsafeCodePointAt0 = M.Data_String_CodePoints_foreign._ end end)(0)(s)) if M.Data_String_CodePoints_conj(M.Data_String_CodePoints_conj(M.Data_String_CodePoints_lessThanOrEq(55296)(cu0))(M.Data_String_CodePoints_lessThanOrEq(cu0)(56319)))((function( ) - if "Data.Ordering∷Ordering.GT" == M.Data_Ord_compare(M.Data_Ord_ordInt)(M.Data_String_CodeUnits_foreign.length(s))(1)["$ctor"] then + if "Data.Ordering∷Ordering.GT" == (M.Data_Ord_compare(M.Data_Ord_ordInt)(M.Data_String_CodeUnits_foreign.length(s))(1))["$ctor"] then return true else return false