INLINE-unfoldings are not the precise RHS.
Consider Bug.hs:
module Bug where
import Data.ByteString (ByteString)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
countChars :: ByteString -> Int
countChars = T.length . T.toUpper . TE.decodeUtf8
{-# INLINE countChars #-}
If I compile this module with GHC-9.8.2 and print the resulting interface
% ghc-9.8.2 -O -fforce-recomp -c Bug.hs
% ghc-9.8.2 --show-iface Bug.hi
I see something like:
countChars ::
Data.ByteString.Internal.Type.ByteString -> GHC.Types.Int
[LambdaFormInfo: LFReEntrant 1, Arity: 1, Strictness: <1L>, CPR: 1,
Inline: (sat-args=0),
Unfolding: Core: StableUser <0,FalseTrue>
\ (eta['GHC.Types.Many] :: Data.ByteString.Internal.Type.ByteString) ->
let {
t :: Data.Text.Internal.Text [] = Data.Text.Encoding.decodeUtf8 eta
} in
case Data.Text.null t of wild {
GHC.Types.False
-> case t of wild1 { Data.Text.Internal.Text bx bx1 bx2 ->
letrec {
....
here, the T.toUpper (which is marked as INLINE) is inlined into the unfolding.
The documention says
So GHC guarantees to inline precisely the code that you wrote, no more and no less. It does this by capturing a copy of the definition of the function to use for inlining (we call this the “inline-RHS”), which it leaves untouched, while optimising the ordinarily RHS as usual. For externally-visible functions the inline-RHS (not the optimised RHS) is recorded in the interface file.
I find the documentation and reality to be different.
I understand that inlining INLINE-marked stuff into inline-RHS makes sense (as those would be inlined), but then the documentation should have a remark. In this case this bloats interface files. T.toUpper is "big".
I don't like that toUpper inner-loop is forcefully inlined (it results in code bloat), but it's another (text's) issue.