Skip to content

Commit

Permalink
Add HasCallStack for partial functions (haskell#440)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bodigrim committed Dec 4, 2021
1 parent 6ac903a commit fac2b09
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 31 deletions.
29 changes: 15 additions & 14 deletions Data/ByteString.hs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ import GHC.IO.BufferedIO as Buffered
import GHC.IO.Encoding (getFileSystemEncoding)
import GHC.IO (unsafePerformIO, unsafeDupablePerformIO)
import GHC.Foreign (newCStringLen, peekCStringLen)
import GHC.Stack.Types (HasCallStack)
import Data.Char (ord)
import Foreign.Marshal.Utils (copyBytes)

Expand Down Expand Up @@ -398,15 +399,15 @@ snoc (BS x l) c = unsafeCreate (l+1) $ \p -> unsafeWithForeignPtr x $ \f -> do

-- | /O(1)/ Extract the first element of a ByteString, which must be non-empty.
-- An exception will be thrown in the case of an empty ByteString.
head :: ByteString -> Word8
head :: HasCallStack => ByteString -> Word8
head (BS x l)
| l <= 0 = errorEmptyList "head"
| otherwise = accursedUnutterablePerformIO $ unsafeWithForeignPtr x $ \p -> peek p
{-# INLINE head #-}

-- | /O(1)/ Extract the elements after the head of a ByteString, which must be non-empty.
-- An exception will be thrown in the case of an empty ByteString.
tail :: ByteString -> ByteString
tail :: HasCallStack => ByteString -> ByteString
tail (BS p l)
| l <= 0 = errorEmptyList "tail"
| otherwise = BS (plusForeignPtr p 1) (l-1)
Expand All @@ -424,7 +425,7 @@ uncons (BS x l)

-- | /O(1)/ Extract the last element of a ByteString, which must be finite and non-empty.
-- An exception will be thrown in the case of an empty ByteString.
last :: ByteString -> Word8
last :: HasCallStack => ByteString -> Word8
last ps@(BS x l)
| null ps = errorEmptyList "last"
| otherwise = accursedUnutterablePerformIO $
Expand All @@ -433,7 +434,7 @@ last ps@(BS x l)

-- | /O(1)/ Return all the elements of a 'ByteString' except the last one.
-- An exception will be thrown in the case of an empty ByteString.
init :: ByteString -> ByteString
init :: HasCallStack => ByteString -> ByteString
init ps@(BS p l)
| null ps = errorEmptyList "init"
| otherwise = BS p (l-1)
Expand Down Expand Up @@ -583,15 +584,15 @@ foldr' k v = \(BS fp len) ->
-- | 'foldl1' is a variant of 'foldl' that has no starting value
-- argument, and thus must be applied to non-empty 'ByteString's.
-- An exception will be thrown in the case of an empty ByteString.
foldl1 :: (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldl1 :: HasCallStack => (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldl1 f ps = case uncons ps of
Nothing -> errorEmptyList "foldl1"
Just (h, t) -> foldl f h t
{-# INLINE foldl1 #-}

-- | 'foldl1'' is like 'foldl1', but strict in the accumulator.
-- An exception will be thrown in the case of an empty ByteString.
foldl1' :: (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldl1' :: HasCallStack => (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldl1' f ps = case uncons ps of
Nothing -> errorEmptyList "foldl1'"
Just (h, t) -> foldl' f h t
Expand All @@ -600,15 +601,15 @@ foldl1' f ps = case uncons ps of
-- | 'foldr1' is a variant of 'foldr' that has no starting value argument,
-- and thus must be applied to non-empty 'ByteString's
-- An exception will be thrown in the case of an empty ByteString.
foldr1 :: (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldr1 :: HasCallStack => (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldr1 f ps = case unsnoc ps of
Nothing -> errorEmptyList "foldr1"
Just (b, c) -> foldr f c b
{-# INLINE foldr1 #-}

-- | 'foldr1'' is a variant of 'foldr1', but is strict in the
-- accumulator.
foldr1' :: (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldr1' :: HasCallStack => (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldr1' f ps = case unsnoc ps of
Nothing -> errorEmptyList "foldr1'"
Just (b, c) -> foldr' f c b
Expand Down Expand Up @@ -683,7 +684,7 @@ all f (BS x len) = accursedUnutterablePerformIO $ unsafeWithForeignPtr x g

-- | /O(n)/ 'maximum' returns the maximum value from a 'ByteString'
-- An exception will be thrown in the case of an empty ByteString.
maximum :: ByteString -> Word8
maximum :: HasCallStack => ByteString -> Word8
maximum xs@(BS x l)
| null xs = errorEmptyList "maximum"
| otherwise = accursedUnutterablePerformIO $ unsafeWithForeignPtr x $ \p ->
Expand All @@ -692,7 +693,7 @@ maximum xs@(BS x l)

-- | /O(n)/ 'minimum' returns the minimum value from a 'ByteString'
-- An exception will be thrown in the case of an empty ByteString.
minimum :: ByteString -> Word8
minimum :: HasCallStack => ByteString -> Word8
minimum xs@(BS x l)
| null xs = errorEmptyList "minimum"
| otherwise = accursedUnutterablePerformIO $ unsafeWithForeignPtr x $ \p ->
Expand Down Expand Up @@ -1238,7 +1239,7 @@ intercalateWithByte c f@(BS ffp l) g@(BS fgp m) = unsafeCreate len $ \ptr ->
-- Indexing ByteStrings

-- | /O(1)/ 'ByteString' index (subscript) operator, starting from 0.
index :: ByteString -> Int -> Word8
index :: HasCallStack => ByteString -> Int -> Word8
index ps n
| n < 0 = moduleError "index" ("negative index: " ++ show n)
| n >= length ps = moduleError "index" ("index too large: " ++ show n
Expand Down Expand Up @@ -2014,15 +2015,15 @@ appendFile = modifyFile AppendMode

-- Common up near identical calls to `error' to reduce the number
-- constant strings created when compiled:
errorEmptyList :: String -> a
errorEmptyList :: HasCallStack => String -> a
errorEmptyList fun = moduleError fun "empty ByteString"
{-# NOINLINE errorEmptyList #-}

moduleError :: String -> String -> a
moduleError :: HasCallStack => String -> String -> a
moduleError fun msg = error (moduleErrorMsg fun msg)
{-# NOINLINE moduleError #-}

moduleErrorIO :: String -> String -> IO a
moduleErrorIO :: HasCallStack => String -> String -> IO a
moduleErrorIO fun msg = throwIO . userError $ moduleErrorMsg fun msg
{-# NOINLINE moduleErrorIO #-}

Expand Down
29 changes: 15 additions & 14 deletions Data/ByteString/Lazy.hs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ import Data.ByteString.Lazy.Internal
import Control.Monad (mplus)
import Data.Word (Word8)
import Data.Int (Int64)
import GHC.Stack.Types (HasCallStack)
import System.IO (Handle,openBinaryFile,stdin,stdout,withBinaryFile,IOMode(..)
,hClose)
import System.IO.Error (mkIOError, illegalOperationErrorType)
Expand Down Expand Up @@ -341,7 +342,7 @@ snoc cs w = foldrChunks Chunk (singleton w) cs
{-# INLINE snoc #-}

-- | /O(1)/ Extract the first element of a ByteString, which must be non-empty.
head :: ByteString -> Word8
head :: HasCallStack => ByteString -> Word8
head Empty = errorEmptyList "head"
head (Chunk c _) = S.unsafeHead c
{-# INLINE head #-}
Expand All @@ -357,7 +358,7 @@ uncons (Chunk c cs)

-- | /O(1)/ Extract the elements after the head of a ByteString, which must be
-- non-empty.
tail :: ByteString -> ByteString
tail :: HasCallStack => ByteString -> ByteString
tail Empty = errorEmptyList "tail"
tail (Chunk c cs)
| S.length c == 1 = cs
Expand All @@ -366,15 +367,15 @@ tail (Chunk c cs)

-- | /O(n\/c)/ Extract the last element of a ByteString, which must be finite
-- and non-empty.
last :: ByteString -> Word8
last :: HasCallStack => ByteString -> Word8
last Empty = errorEmptyList "last"
last (Chunk c0 cs0) = go c0 cs0
where go c Empty = S.unsafeLast c
go _ (Chunk c cs) = go c cs
-- XXX Don't inline this. Something breaks with 6.8.2 (haven't investigated yet)

-- | /O(n\/c)/ Return all the elements of a 'ByteString' except the last one.
init :: ByteString -> ByteString
init :: HasCallStack => ByteString -> ByteString
init Empty = errorEmptyList "init"
init (Chunk c0 cs0) = go c0 cs0
where go c Empty | S.length c == 1 = Empty
Expand Down Expand Up @@ -474,18 +475,18 @@ foldr' f a = go

-- | 'foldl1' is a variant of 'foldl' that has no starting value
-- argument, and thus must be applied to non-empty 'ByteString's.
foldl1 :: (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldl1 :: HasCallStack => (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldl1 _ Empty = errorEmptyList "foldl1"
foldl1 f (Chunk c cs) = foldl f (S.unsafeHead c) (Chunk (S.unsafeTail c) cs)

-- | 'foldl1'' is like 'foldl1', but strict in the accumulator.
foldl1' :: (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldl1' :: HasCallStack => (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldl1' _ Empty = errorEmptyList "foldl1'"
foldl1' f (Chunk c cs) = foldl' f (S.unsafeHead c) (Chunk (S.unsafeTail c) cs)

-- | 'foldr1' is a variant of 'foldr' that has no starting value argument,
-- and thus must be applied to non-empty 'ByteString's
foldr1 :: (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldr1 :: HasCallStack => (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldr1 _ Empty = errorEmptyList "foldr1"
foldr1 f (Chunk c0 cs0) = go c0 cs0
where go c Empty = S.foldr1 f c
Expand All @@ -494,7 +495,7 @@ foldr1 f (Chunk c0 cs0) = go c0 cs0
-- | 'foldr1'' is like 'foldr1', but strict in the accumulator.
--
-- @since 0.11.2.0
foldr1' :: (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldr1' :: HasCallStack => (Word8 -> Word8 -> Word8) -> ByteString -> Word8
foldr1' _ Empty = errorEmptyList "foldr1'"
foldr1' f (Chunk c0 cs0) = go c0 cs0
where go c Empty = S.foldr1' f c
Expand Down Expand Up @@ -535,14 +536,14 @@ all f = foldrChunks (\c rest -> S.all f c && rest) True
{-# INLINE all #-}

-- | /O(n)/ 'maximum' returns the maximum value from a 'ByteString'
maximum :: ByteString -> Word8
maximum :: HasCallStack => ByteString -> Word8
maximum Empty = errorEmptyList "maximum"
maximum (Chunk c cs) = foldlChunks (\n c' -> n `max` S.maximum c')
(S.maximum c) cs
{-# INLINE maximum #-}

-- | /O(n)/ 'minimum' returns the minimum value from a 'ByteString'
minimum :: ByteString -> Word8
minimum :: HasCallStack => ByteString -> Word8
minimum Empty = errorEmptyList "minimum"
minimum (Chunk c cs) = foldlChunks (\n c' -> n `min` S.minimum c')
(S.minimum c) cs
Expand Down Expand Up @@ -715,7 +716,7 @@ replicate n w
-- | 'cycle' ties a finite ByteString into a circular one, or equivalently,
-- the infinite repetition of the original ByteString.
--
cycle :: ByteString -> ByteString
cycle :: HasCallStack => ByteString -> ByteString
cycle Empty = errorEmptyList "cycle"
cycle cs = cs' where cs' = foldrChunks Chunk cs' cs

Expand Down Expand Up @@ -1122,7 +1123,7 @@ intercalate s = concat . List.intersperse s
-- Indexing ByteStrings

-- | /O(c)/ 'ByteString' index (subscript) operator, starting from 0.
index :: ByteString -> Int64 -> Word8
index :: HasCallStack => ByteString -> Int64 -> Word8
index _ i | i < 0 = moduleError "index" ("negative index: " ++ show i)
index cs0 i = index' cs0 i
where index' Empty n = moduleError "index" ("index too large: " ++ show n)
Expand Down Expand Up @@ -1613,11 +1614,11 @@ interact transformer = putStr . transformer =<< getContents

-- Common up near identical calls to `error' to reduce the number
-- constant strings created when compiled:
errorEmptyList :: String -> a
errorEmptyList :: HasCallStack => String -> a
errorEmptyList fun = moduleError fun "empty ByteString"
{-# NOINLINE errorEmptyList #-}

moduleError :: String -> String -> a
moduleError :: HasCallStack => String -> String -> a
moduleError fun msg = error ("Data.ByteString.Lazy." ++ fun ++ ':':' ':msg)
{-# NOINLINE moduleError #-}

Expand Down
7 changes: 4 additions & 3 deletions Data/ByteString/Short/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import GHC.Exts ( Int(I#), Int#, Ptr(Ptr), Addr#, Char(C#)
import GHC.IO
import GHC.ForeignPtr (ForeignPtr(ForeignPtr), ForeignPtrContents(PlainPtr))
import GHC.ST (ST(ST), runST)
import GHC.Stack.Types (HasCallStack)
import GHC.Word

import Prelude ( Eq(..), Ord(..), Ordering(..), Read(..), Show(..)
Expand Down Expand Up @@ -194,7 +195,7 @@ null :: ShortByteString -> Bool
null sbs = length sbs == 0

-- | /O(1)/ 'ShortByteString' index (subscript) operator, starting from 0.
index :: ShortByteString -> Int -> Word8
index :: HasCallStack => ShortByteString -> Int -> Word8
index sbs i
| i >= 0 && i < length sbs = unsafeIndex sbs i
| otherwise = indexError sbs i
Expand Down Expand Up @@ -222,7 +223,7 @@ indexMaybe sbs i
unsafeIndex :: ShortByteString -> Int -> Word8
unsafeIndex sbs = indexWord8Array (asBA sbs)

indexError :: ShortByteString -> Int -> a
indexError :: HasCallStack => ShortByteString -> Int -> a
indexError sbs i =
error $ "Data.ByteString.Short.index: error in array index; " ++ show i
++ " not in range [0.." ++ show (length sbs) ++ ")"
Expand Down Expand Up @@ -601,7 +602,7 @@ useAsCStringLen bs action =
-- ---------------------------------------------------------------------
-- Internal utilities

moduleErrorIO :: String -> String -> IO a
moduleErrorIO :: HasCallStack => String -> String -> IO a
moduleErrorIO fun msg = throwIO . userError $ moduleErrorMsg fun msg
{-# NOINLINE moduleErrorIO #-}

Expand Down

0 comments on commit fac2b09

Please sign in to comment.