Problem
PrimLen is the IR node for Lua's unary # operator — the element count of a table or the byte count of a string. Constant folding handles it over a manifest array but not over a manifest string. Verbatim from Language.PureScript.Backend.IR.Optimizer:
reduceArrayRead ∷ Applicative m ⇒ RewriteRuleM m Ann
reduceArrayRead =
pure . \case
PrimLen ann (LiteralArray _ elements) →
Just $ LiteralInt ann (fromIntegral (length elements))
ArrayIndex ann (LiteralArray _ elements) index →
setAnn ann <$> elements !!? fromIntegral index
_ → Nothing
There is no PrimLen ann (LiteralString _ s) case, so a string length over a literal survives to the output. Since #247 lifted Data.String.CodeUnits.length into this node, the gap is now reachable from ordinary source. Golden.LengthLift calls widthOf "hello" where widthOf = CodeUnits.length, and the emitted Lua is:
local _ = Effect_Console_log(Data_Show_showIntImpl(#("hello")))()
The array case beside it folds all the way, from the same golden's countOf [10, 20, 30]:
local _ = Effect_Console_log(Data_Show_showIntImpl(3))()
So #("hello") should be 5.
Approach
Add the LiteralString case. The subtlety is which length to use: in pslua a PureScript String is a Lua byte string holding UTF-8 (see docs/QUIRKS.md), so #s at runtime is the byte count of the encoded literal, not the character count of the Haskell Text the IR carries. Folding to Text.length would be wrong for any literal outside ASCII — #("é") is 2, not 1. The fold must therefore measure the literal exactly as the printer encodes it, which is the same encoding decision the string printer already makes, including the lone-surrogate/WTF-8 path the codebase handles deliberately. Reusing that encoder rather than re-deriving a byte count is what keeps the fold and the emitted literal from disagreeing.
Data.String.CodeUnits.length is the operation whose PureScript semantics this matches: it counts code units, and in this backend a code unit is a byte.
Verification / Measurement
A folding unit test per class in the optimizer spec: an ASCII literal (#"hello" → 5), a multi-byte literal where the byte count and the character count differ (so a Text.length implementation fails the test), and a lone-surrogate literal to pin agreement with the printer's escape path.
Golden.LengthLift's Lua golden then moves from #("hello") to 5, with its eval/golden.txt oracle unchanged at 5 — the oracle is what proves the fold computed the same number the runtime did.
Prerequisites / Relations
Independent. Follows #247, which made the node reachable from string source.
Problem
PrimLenis the IR node for Lua's unary#operator — the element count of a table or the byte count of a string. Constant folding handles it over a manifest array but not over a manifest string. Verbatim fromLanguage.PureScript.Backend.IR.Optimizer:There is no
PrimLen ann (LiteralString _ s)case, so a string length over a literal survives to the output. Since #247 liftedData.String.CodeUnits.lengthinto this node, the gap is now reachable from ordinary source.Golden.LengthLiftcallswidthOf "hello"wherewidthOf = CodeUnits.length, and the emitted Lua is:The array case beside it folds all the way, from the same golden's
countOf [10, 20, 30]:So
#("hello")should be5.Approach
Add the
LiteralStringcase. The subtlety is which length to use: inpsluaa PureScriptStringis a Lua byte string holding UTF-8 (seedocs/QUIRKS.md), so#sat runtime is the byte count of the encoded literal, not the character count of the HaskellTextthe IR carries. Folding toText.lengthwould be wrong for any literal outside ASCII —#("é")is 2, not 1. The fold must therefore measure the literal exactly as the printer encodes it, which is the same encoding decision the string printer already makes, including the lone-surrogate/WTF-8 path the codebase handles deliberately. Reusing that encoder rather than re-deriving a byte count is what keeps the fold and the emitted literal from disagreeing.Data.String.CodeUnits.lengthis the operation whose PureScript semantics this matches: it counts code units, and in this backend a code unit is a byte.Verification / Measurement
A folding unit test per class in the optimizer spec: an ASCII literal (
#"hello"→5), a multi-byte literal where the byte count and the character count differ (so aText.lengthimplementation fails the test), and a lone-surrogate literal to pin agreement with the printer's escape path.Golden.LengthLift's Lua golden then moves from#("hello")to5, with itseval/golden.txtoracle unchanged at5— the oracle is what proves the fold computed the same number the runtime did.Prerequisites / Relations
Independent. Follows #247, which made the node reachable from string source.