diff --git a/changelog.d/20260724_120000_unisay_fold_literal_array_reads.md b/changelog.d/20260724_120000_unisay_fold_literal_array_reads.md new file mode 100644 index 00000000..17e05f2f --- /dev/null +++ b/changelog.d/20260724_120000_unisay_fold_literal_array_reads.md @@ -0,0 +1,23 @@ +### Added + +- Array-length and indexing reads over manifest array literals fold at + compile time (#225). A pattern match on a fixed-length array whose + scrutinee is a literal array — `case [10, 20] of [a, b] → a + b; _ → -1` + — compiled to a runtime length check and element reads (`if 2 == #(v) + then return v[1] + v[2] else return -1 end`), although every part is + statically known. Two additions reduce it. The new `reduceArrayRead` + rule — the `reduceObjectProp` sibling for arrays — folds `arrayLength` + over an in-place `LiteralArray` to the element count and an in-range + `ArrayIndex` to the element itself. And the new + `propagateKnownArrayThroughLet` rule — the literal-array sibling of the + known-constructor propagation (#214) — carries a let-bound array literal + into the binder's length and index reads, binding each read element once + to a fresh element-binder so no element is duplicated or re-evaluated. + The existing folds then finish the collapse (`2 == 2` to `true`, + unreachable-else removal, constant arithmetic), leaving `30` for the + shape above. The length folds against the IR element count, which is + exact — a `LiteralArray` codegens to a hole-free positional table — so + no reasoning about Lua's `#` over tables with holes is involved. A match + on an array of unknown length is untouched, an out-of-range read + declines, and a binder also read as a whole value keeps its binding. + Eval goldens are unchanged. diff --git a/lib/Language/PureScript/Backend/IR/Optimizer.hs b/lib/Language/PureScript/Backend/IR/Optimizer.hs index e9c3ef6d..d0a03d7f 100644 --- a/lib/Language/PureScript/Backend/IR/Optimizer.hs +++ b/lib/Language/PureScript/Backend/IR/Optimizer.hs @@ -1013,6 +1013,7 @@ optimizedExpressionWithPastes ctorTags pastes policy env = ( canonicalizeEffectHead `thenRewrite` constantFolding `thenRewrite` reduceObjectProp + `thenRewrite` reduceArrayRead `thenRewrite` sinkProjectionIntoLet `thenRewrite` cancelLetValuesOfValues `thenRewrite` floatLetFromLetValuesRhs @@ -1021,6 +1022,7 @@ optimizedExpressionWithPastes ctorTags pastes policy env = `thenRewrite` reduceKnownConstructor `thenRewrite` reduceKnownCtorRefRead env `thenRewrite` propagateKnownCtorThroughLet env + `thenRewrite` propagateKnownArrayThroughLet `thenRewrite` resolveDictionaryProp pastes policy env `thenRewrite` inlineAnnotatedProjection policy env `thenRewrite` inlineSaturatedCall pastes policy env @@ -1231,6 +1233,32 @@ reduceObjectProp = Nothing → ObjectProp ann obj prop _ → Nothing +{- | Folds array reads over an in-place array literal, the +'reduceObjectProp' sibling for arrays (issue #225): the length of a +literal array is its element count, and an in-range index reads the +element directly. + +The length folds against the IR element count, which is exact — a +'LiteralArray' codegens to a hole-free positional table, so Lua's @#@ +agrees by construction; no reasoning about @#@ over tables with holes +is involved. An out-of-range index reads no existing element (possible +only on ill-typed input, see Note [IR is assumed well-typed]), so the +rule declines, as 'reduceObjectProp' declines a missing label. + +The discarded elements are never evaluated — the same call DCE makes +when it drops an unused binding unconditionally. The folded value takes +the read node's own annotation, not the element's, for the reason +spelled out on 'reduceObjectProp'. +-} +reduceArrayRead ∷ Applicative m ⇒ RewriteRuleM m Ann +reduceArrayRead = + pure . \case + ArrayLength ann (LiteralArray _ elements) → + Just $ LiteralInt ann (fromIntegral (length elements)) + ArrayIndex ann (LiteralArray _ elements) index → + setAnn ann <$> elements !!? fromIntegral index + _ → Nothing + {- | @(let … in body).label ===> let … in body.label@ Bindings evaluate first either way, so the move is behaviour-preserving @@ -1573,10 +1601,6 @@ propagateKnownCtorThroughLet env = \case Just (reverse before, (name, algTy, arity, args, tag), after) _ → go (grouping : before) after - countFreeRefGrouping ∷ Name → Grouping (Ann, Name, Exp) → Natural - countFreeRefGrouping name grouping = - sum [countFreeRef (Local name) e | (_ann, _n, e) ← listGrouping grouping] - readFieldIndices ∷ Name → AlgebraicType → Exp → Set Natural readFieldIndices name algTy = go where @@ -1605,6 +1629,123 @@ propagateKnownCtorThroughLet env = \case Ref daAnn (Local f) other → over subexpressions go other +-- | Free references to the name across a grouping's right-hand sides. +countFreeRefGrouping ∷ Name → Grouping (Ann, Name, Exp) → Natural +countFreeRefGrouping name grouping = + sum [countFreeRef (Local name) e | (_ann, _n, e) ← listGrouping grouping] + +{- | The literal-array sibling of 'propagateKnownCtorThroughLet' (issue +#225). A fixed-length array pattern reads its scrutinee several times — +one length check plus one read per bound element — so a manifest array +scrutinee is never in place for 'reduceArrayRead' to fold: the match on +@case [10, 20] of [a, b] → …@ lands on + +> let v = [10, 20] in +> if 2 == arrayLength v then … v[0] … v[1] … else fallthrough + +and nothing folds: the rules see @ArrayLength (Ref v)@ and +@ArrayIndex (Ref v) i@, never the literal. + +This propagates the literal through a 'Standalone' Let binding into the +binder's reads. When the binder is read /only/ through in-range array +reads: + + * each length read becomes the element count (exact for the reason + spelled out on 'reduceArrayRead'); + * each element read (@ArrayIndex v i@, in range) becomes a fresh + element-binder @eᵢ@ bound once to the iᵗʰ element — the + field-binder discipline of 'propagateKnownCtorThroughLet', so an + element read at several sites is evaluated once, not duplicated; + * the @v@ binding is dropped, its unread elements discarded with the + same licence 'reduceArrayRead' spells out. + +Trivial and dead element-binders then inline or DCE away, and the +folded reads let the surrounding @Eq@ / @if@ meet 'constantFolding' and +'removeUnreachable*', collapsing the match to its live arm. + +The rule declines when the binder is read as a whole value — a sibling +RHS, a non-eliminating position such as an argument to a function, or +an out-of-range index (it reads no element, so there is nothing to bind +for it): dropping the binding would dangle the reference, and keeping +it while binding the elements would duplicate them. GUC keeps the fresh +element-binders unique and the binder resolved by name. +-} +propagateKnownArrayThroughLet ∷ RewriteRuleM SupplyM Ann +propagateKnownArrayThroughLet = \case + Let ann groupings body + | Just (before, (name, elements), after) ← + findArrayBinding (toList groupings) + , all ((== 0) . countFreeRefGrouping name) (before <> after) + , countFreeRef (Local name) body > 0 + , let len = fromIntegral (length elements) ∷ Natural + , not (Query.hasWholeValueArrayRead name len body) → do + let readIndices = readElementIndices name body + freshElements ← + Map.fromList + <$> traverse + (\i → (i,) <$> freshName "$elem") + (toList readIndices) + let body' = foldArrayReads name len freshElements body + elementBinds = + [ Standalone (noAnn, e, element) + | (i, e) ← Map.toAscList freshElements + , Just element ← [elements !!? fromIntegral i] + ] + pure . Just $ case nonEmpty (before <> elementBinds <> after) of + Nothing → body' + Just gs → Let ann gs body' + _ → pure Nothing + where + -- The first Standalone binding whose RHS is an array literal, split + -- out from its siblings. + findArrayBinding + ∷ [Grouping (Ann, Name, Exp)] + → Maybe + ( [Grouping (Ann, Name, Exp)] + , (Name, [Exp]) + , [Grouping (Ann, Name, Exp)] + ) + findArrayBinding = go [] + where + go + ∷ [Grouping (Ann, Name, Exp)] + → [Grouping (Ann, Name, Exp)] + → Maybe + ( [Grouping (Ann, Name, Exp)] + , (Name, [Exp]) + , [Grouping (Ann, Name, Exp)] + ) + go _before [] = Nothing + go before (grouping : after) = case grouping of + Standalone (_bAnn, name, rhs@(LiteralArray _ elements)) + -- A self-referencing RHS cannot arise under GUC (a Standalone RHS + -- does not see its own binder), but 'optimizedExpression' also runs + -- on non-GUC input; dropping the binding would then dangle the + -- element-binder that copied the reference (cf. 'inlineLocalBinding'). + | countFreeRef (Local name) rhs == 0 → + Just (reverse before, (name, elements), after) + _ → go (grouping : before) after + + readElementIndices ∷ Name → Exp → Set Natural + readElementIndices name = go + where + go e = self e <> foldMap go (toListOf subexpressions e) + self = \case + ArrayIndex _ (Ref _ (Local n)) i | n == name → Set.singleton i + _ → mempty + + foldArrayReads ∷ Name → Natural → Map Natural Name → Exp → Exp + foldArrayReads name len freshElements = go + where + go = \case + ArrayLength alAnn (Ref _ (Local n)) + | n == name → LiteralInt alAnn (fromIntegral len) + ArrayIndex aiAnn (Ref _ (Local n)) i + | n == name + , Just e ← Map.lookup i freshElements → + Ref aiAnn (Local e) + other → over subexpressions go other + {- | The through-a-reference companion of 'reduceKnownConstructor' — the relationship 'resolveDictionaryProp' bears to 'reduceObjectProp'. A constructor value used at a saturated site is a reference-headed call: the diff --git a/lib/Language/PureScript/Backend/IR/Query.hs b/lib/Language/PureScript/Backend/IR/Query.hs index 3fdbc4d4..b1e4b214 100644 --- a/lib/Language/PureScript/Backend/IR/Query.hs +++ b/lib/Language/PureScript/Backend/IR/Query.hs @@ -210,3 +210,21 @@ hasWholeValueRead name algTy arity = go | n == name, readTy == algTy, i < arity → False Ref _ (Local n) | n == name → True other → any go (toListOf subexpressions other) + +{- | The array sibling of 'hasWholeValueRead', for a binder whose +right-hand side is a literal array of the given length: whether some +@Ref name@ is reached other than through a foldable array read — a +length read, or an element read at an in-range index. An out-of-range +index reads no existing element, so it counts as a whole-value read, +forcing the caller to decline; well-typed input never indexes past a +matched length, but the guard keeps the caller sound on the non-GUC / +generated input 'optimizedExpression' also runs on. +-} +hasWholeValueArrayRead ∷ Name → Natural → Exp → Bool +hasWholeValueArrayRead name len = go + where + go = \case + ArrayLength _ (Ref _ (Local n)) | n == name → False + ArrayIndex _ (Ref _ (Local n)) i | n == name, i < len → False + Ref _ (Local n) | n == name → True + other → any go (toListOf subexpressions other) diff --git a/test/Language/PureScript/Backend/IR/Optimizer/Spec.hs b/test/Language/PureScript/Backend/IR/Optimizer/Spec.hs index 6251e8d6..2c151eac 100644 --- a/test/Language/PureScript/Backend/IR/Optimizer/Spec.hs +++ b/test/Language/PureScript/Backend/IR/Optimizer/Spec.hs @@ -2,7 +2,7 @@ module Language.PureScript.Backend.IR.Optimizer.Spec where import Data.Map qualified as Map import Data.Text qualified as Text -import Hedgehog (PropertyT, annotateShow, diff, forAll, (===)) +import Hedgehog (PropertyT, annotateShow, diff, evalEither, forAll, (===)) import Hedgehog.Gen qualified as Gen import Hedgehog.Range qualified as Range import Language.PureScript.Backend.IR.Gen qualified as Gen @@ -54,6 +54,8 @@ import Language.PureScript.Backend.IR.Types , alphaEq , application , applicationN + , arrayIndex + , arrayLength , countFreeRef , countFreeRefUsage , countFreeRefs @@ -67,6 +69,7 @@ import Language.PureScript.Backend.IR.Types , isLiteral , lets , listGrouping + , literalArray , literalBool , literalChar , literalFloat @@ -1207,6 +1210,155 @@ spec = describe "IR Optimizer" do ifThenElse (tagTest ctorC) (literalInt 3) deadDefault optimizedExpression original `shouldBe` original + -- A pattern match on a fixed-length array whose scrutinee is a + -- manifest array literal is decidable at compile time: the scrutinee + -- is let-bound (one length read plus one read per element), the + -- length is the IR element count, and the reads are the elements. + -- With those folded, the existing equality/branch folds collapse the + -- match to its live arm. + describe "folds array length and indexing on literal arrays (#225)" do + let m = moduleNameFromString "M" + v = refLocal (Name "v") + + -- An application: re-evaluating it would repeat the work, so it + -- is the shape the element-binder must not duplicate. + nonTrivial = application (refImported m (Name "g")) (literalInt 1) + + -- The optimized bodies of 'main' after the checked pipeline + -- (mirrors the #214 end-to-end test): 'main' is exported twice + -- so it is not itself inlined away, leaving its body to inspect. + mainMod = moduleNameFromString "Main" + collapsedMainBodies body = + optimizedUberModuleChecked + mempty + Linker.UberModule + { uberModuleForeigns = [] + , uberModuleBindings = + [Standalone (QName mainMod (Name "main"), body)] + , uberModuleExports = + [ (Name "r1", refImported mainMod (Name "main")) + , (Name "r2", refImported mainMod (Name "main")) + ] + } + <&> \optimized → + [ e + | Standalone (QName _ (Name "main"), e) ← + Linker.uberModuleBindings optimized + ] + + it "folds the length of an in-place literal array" do + optimizedExpression + (arrayLength (literalArray [literalInt 10, literalInt 20])) + `shouldBe` literalInt 2 + + it "folds the length over non-trivial elements, dropping them" do + -- The elements are never evaluated: the same licence + -- reduceObjectProp has for a projection's discarded fields. + optimizedExpression (arrayLength (literalArray [nonTrivial])) + `shouldBe` literalInt 1 + + it "folds an in-range index into an in-place literal array" do + optimizedExpression + (arrayIndex (literalArray [literalInt 10, literalInt 20]) 1) + `shouldBe` literalInt 20 + + it "declines an out-of-range index into a literal array" do + -- Reads no existing element (ill-typed input): left to the runtime. + let original = arrayIndex (literalArray [literalInt 10]) 1 + optimizedExpression original `shouldBe` original + + it "collapses a literal-array match to its result" do + -- The motivating shape: case [10, 20] of [a, b] → a + b; _ → -1. + -- The scrutinee is read three times, so only the through-the-let + -- propagation can unlock the folds. End to end through the checked + -- pipeline: it runs DCE (the rewrite chain leaves the spent + -- element-binders for DCE to drop) and lints every pass's + -- contract. The result is kept under a call so 'main' stays + -- non-trivial (a bare literal would inline into the exports). + let original = + let1 (Name "v") (literalArray [literalInt 10, literalInt 20]) $ + ifThenElse + (eq (literalInt 2) (arrayLength v)) + ( application + (refImported mainMod (Name "use")) + (primBinOp PrimAdd (arrayIndex v 0) (arrayIndex v 1)) + ) + (literalInt (-1)) + mainBodies ← either (fail . show) pure (collapsedMainBodies original) + mainBodies + `shouldBe` [application (refImported mainMod (Name "use")) (literalInt 30)] + + it "leaves a match on an unknown array untouched" do + let xs = refLocal (Name "xs") + original = + ifThenElse + (eq (literalInt 2) (arrayLength xs)) + (primBinOp PrimAdd (arrayIndex xs 0) (arrayIndex xs 1)) + (literalInt (-1)) + optimizedExpression original `shouldBe` original + + it "binds an element read at several sites once (no duplication)" do + -- v's element read twice with a non-trivial value: the element is + -- bound to one element-binder read twice, never copied to each site. + let original = + let1 (Name "v") (literalArray [nonTrivial]) $ + application + (application (refImported m (Name "pair")) (arrayIndex v 0)) + (arrayIndex v 0) + element = Name "$elem0" + expected = + let1 + element + nonTrivial + ( application + (application (refImported m (Name "pair")) (refLocal element)) + (refLocal element) + ) + optimizedExpression original `shouldSatisfy` alphaEq expected + + it "declines when the binder is read as a whole value" do + -- v also flows into a function whole, so dropping the binding + -- would dangle the reference; the binding survives untouched. + let original = + let1 (Name "v") (literalArray [literalInt 1]) $ + application + (application (refImported m (Name "pair")) (arrayIndex v 0)) + v + optimizedExpression original `shouldBe` original + + prop "collapses to the live arm across scalar elements" do + -- Across scalar elements, the match folds all the way down: the + -- length check dissolves, the element-binder (trivial) inlines, + -- and the dead arm drops — proof the rule fired, since with the + -- array Let-bound and read twice nothing else can unlock it. + -- Through the checked pipeline, so a GUC or scoping violation + -- from folding through the Let fails here rather than pass + -- silently. + element ← forAll Gen.scalarExp + let original = + let1 (Name "v") (literalArray [element]) $ + ifThenElse + (eq (literalInt 1) (arrayLength v)) + (application (refImported mainMod (Name "use")) (arrayIndex v 0)) + (literalInt (-1)) + mainBodies ← evalEither (collapsedMainBodies original) + mainBodies === [application (refImported mainMod (Name "use")) element] + + prop "declines an out-of-range element read, staying well-scoped" do + -- An index past the array's length reads no existing element: + -- folding it would mint an element-binder the literal cannot bind, + -- then drop the array binding, stranding both. Fuzzed structurally + -- (the length is random and the index may exceed it) with + -- well-scopedness as the oracle, as for the constructor sibling. + len ← forAll (Gen.int (Range.linear 0 3)) + index ← forAll (Gen.integral (Range.linear 0 5)) + let original = + let1 (Name "v") (literalArray (replicate len (literalInt 1))) $ + application + (application (refImported m (Name "pair")) (arrayIndex v 0)) + (arrayIndex v index) + unboundLocals (optimizedExpression original) === [] + -- Case-of-case over the IfThenElse decision tree: a boolean-returning -- tree consumed in a strict position (an Eq against a literal, or the -- condition of another if) sits in expression position, where codegen diff --git a/test/ps/output/Golden.ArrayPatternMatch.Test/golden.ir b/test/ps/output/Golden.ArrayPatternMatch.Test/golden.ir index 44f61bdf..bae737d0 100644 --- a/test/ps/output/Golden.ArrayPatternMatch.Test/golden.ir +++ b/test/ps/output/Golden.ArrayPatternMatch.Test/golden.ir @@ -7,6 +7,12 @@ UberModule ( ModuleName "Data.Show" ) ".spago/p/prelude/5718c84fdde6247749cb053e816df696c30fe691/src/Data/Show.purs" [ ( Nothing, Name "showIntImpl" ) ] ), Standalone + ( QName + { qnameModuleName = ModuleName "Data.Show", qnameName = Name "showIntImpl" + }, ObjectProp Nothing + ( Ref Nothing ( Imported ( ModuleName "Data.Show" ) ( Name "foreign" ) ) ) + ( PropName "showIntImpl" ) + ), Standalone ( QName { qnameModuleName = ModuleName "Effect.Console", qnameName = Name "foreign" }, ForeignImport Nothing @@ -14,22 +20,10 @@ UberModule [ ( Nothing, Name "log" ) ] ), Standalone ( QName - { qnameModuleName = ModuleName "Golden.ArrayPatternMatch.Test", qnameName = Name "logShow" - }, AbsN Nothing - ( ParamNamed Nothing ( Name "a$2" ) :| [] ) - ( AppN Nothing - ( ObjectProp Nothing - ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "foreign" ) ) ) - ( PropName "log" ) - ) - ( AppN Nothing - ( ObjectProp Nothing - ( Ref Nothing ( Imported ( ModuleName "Data.Show" ) ( Name "foreign" ) ) ) - ( PropName "showIntImpl" ) - ) - ( Ref Nothing ( Local ( Name "a$2" ) ) :| [] ) :| [] - ) - ) + { qnameModuleName = ModuleName "Effect.Console", qnameName = Name "log" + }, ObjectProp Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "foreign" ) ) ) + ( PropName "log" ) ), Standalone ( QName { qnameModuleName = ModuleName "Golden.ArrayPatternMatch.Test", qnameName = Name "lastOfThree" @@ -74,16 +68,10 @@ UberModule ( Standalone ( Nothing, Name "_", AppN Nothing ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.ArrayPatternMatch.Test" ) ( Name "logShow" ) ) - ) + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.ArrayPatternMatch.Test" ) ( Name "firstTwo" ) ) - ) - ( LiteralArray Nothing - [ LiteralInt Nothing 10, LiteralInt Nothing 20 ] :| [] - ) :| [] + ( Ref Nothing ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) ) + ( LiteralInt Nothing 30 :| [] ) :| [] ) ) ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) @@ -91,36 +79,20 @@ UberModule [ Standalone ( Nothing, Name "_", AppN Nothing ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.ArrayPatternMatch.Test" ) ( Name "logShow" ) ) - ) + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) ( AppN Nothing - ( Ref Nothing - ( Imported - ( ModuleName "Golden.ArrayPatternMatch.Test" ) - ( Name "firstTwo" ) - ) - ) - ( LiteralArray Nothing - [ LiteralInt Nothing 1, LiteralInt Nothing 2, LiteralInt Nothing 3 ] :| [] - ) :| [] + ( Ref Nothing ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) ) + ( LiteralInt Nothing ( -1 ) :| [] ) :| [] ) ) ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) ), Standalone ( Nothing, Name "_", AppN Nothing ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.ArrayPatternMatch.Test" ) ( Name "logShow" ) ) - ) + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) ( AppN Nothing - ( Ref Nothing - ( Imported - ( ModuleName "Golden.ArrayPatternMatch.Test" ) - ( Name "firstTwo" ) - ) - ) - ( LiteralArray Nothing [] :| [] ) :| [] + ( Ref Nothing ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) ) + ( LiteralInt Nothing ( -1 ) :| [] ) :| [] ) ) ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) @@ -129,16 +101,10 @@ UberModule ) ( AppN Nothing ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.ArrayPatternMatch.Test" ) ( Name "logShow" ) ) - ) + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.ArrayPatternMatch.Test" ) ( Name "lastOfThree" ) ) - ) - ( LiteralArray Nothing - [ LiteralInt Nothing 7, LiteralInt Nothing 8, LiteralInt Nothing 9 ] :| [] - ) :| [] + ( Ref Nothing ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) ) + ( LiteralInt Nothing 9 :| [] ) :| [] ) ) ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) diff --git a/test/ps/output/Golden.ArrayPatternMatch.Test/golden.lua b/test/ps/output/Golden.ArrayPatternMatch.Test/golden.lua index 4ba3b0bf..bb7b82dc 100644 --- a/test/ps/output/Golden.ArrayPatternMatch.Test/golden.lua +++ b/test/ps/output/Golden.ArrayPatternMatch.Test/golden.lua @@ -1,30 +1,19 @@ +local M = {} local Data_Show_foreign = { showIntImpl = function(n) return tostring(n) end } +local Data_Show_showIntImpl = Data_Show_foreign.showIntImpl local Effect_Console_foreign = { log = function(s) return function() print(s) end end } -local Golden_ArrayPatternMatch_Test_logShow = function(a_S_2) - return Effect_Console_foreign.log(Data_Show_foreign.showIntImpl(a_S_2)) -end -local Golden_ArrayPatternMatch_Test_lastOfThree = function(v) +local Effect_Console_log = Effect_Console_foreign.log +M.Golden_ArrayPatternMatch_Test_lastOfThree = function(v) if 3 == #(v) then return v[3] else return -1 end end -local Golden_ArrayPatternMatch_Test_firstTwo = function(v) +M.Golden_ArrayPatternMatch_Test_firstTwo = function(v) if 2 == #(v) then return v[1] + v[2] else return -1 end end return (function() - local _ = Golden_ArrayPatternMatch_Test_logShow(Golden_ArrayPatternMatch_Test_firstTwo({ - [1] = 10, - [2] = 20 - }))() - local _ = Golden_ArrayPatternMatch_Test_logShow(Golden_ArrayPatternMatch_Test_firstTwo({ - [1] = 1, - [2] = 2, - [3] = 3 - }))() - local _ = Golden_ArrayPatternMatch_Test_logShow(Golden_ArrayPatternMatch_Test_firstTwo({}))() - return Golden_ArrayPatternMatch_Test_logShow(Golden_ArrayPatternMatch_Test_lastOfThree({ - [1] = 7, - [2] = 8, - [3] = 9 - }))() + local _ = Effect_Console_log(Data_Show_showIntImpl(30))() + local _ = Effect_Console_log(Data_Show_showIntImpl(-1))() + local _ = Effect_Console_log(Data_Show_showIntImpl(-1))() + return Effect_Console_log(Data_Show_showIntImpl(9))() end)()