Bodigrim pushed to branch wip/warning-for-last-and-init at Glasgow Haskell Compiler / GHC
Commits:
-
8f16552d
by Mike Pilgrem at 2025-07-12T01:04:22+01:00
17 changed files:
- compiler/GHC/Core/Utils.hs
- compiler/GHC/Driver/Session/Units.hs
- compiler/GHC/Prelude/Basic.hs
- ghc/GHCi/UI.hs
- ghc/Main.hs
- libraries/ghc-boot-th/GHC/Boot/TH/Ppr.hs
- libraries/ghc-internal/src/GHC/Internal/Float.hs
- libraries/ghc-internal/src/GHC/Internal/List.hs
- libraries/ghc-internal/src/GHC/Internal/System/IO.hs
- libraries/ghc-internal/src/GHC/Internal/TH/Syntax.hs
- libraries/template-haskell/vendored-filepath/System/FilePath/Posix.hs
- libraries/template-haskell/vendored-filepath/System/FilePath/Windows.hs
- testsuite/tests/driver/j-space/jspace.hs
- testsuite/tests/rts/KeepCafsBase.hs
- utils/check-exact/Utils.hs
- utils/haddock/haddock-api/src/Haddock/Backends/Xhtml.hs
- utils/haddock/haddock-library/src/Documentation/Haddock/Parser.hs
Changes:
... | ... | @@ -112,8 +112,7 @@ import GHC.Utils.Misc |
112 | 112 | |
113 | 113 | import Data.ByteString ( ByteString )
|
114 | 114 | import Data.Function ( on )
|
115 | -import Data.List ( sort, sortBy, partition, zipWith4, mapAccumL )
|
|
116 | -import qualified Data.List as Partial ( init, last )
|
|
115 | +import Data.List ( sort, sortBy, partition, zipWith4, mapAccumL, unsnoc )
|
|
117 | 116 | import Data.Ord ( comparing )
|
118 | 117 | import Control.Monad ( guard )
|
119 | 118 | import qualified Data.Set as Set
|
... | ... | @@ -1871,10 +1870,10 @@ app_ok fun_ok primop_ok fun args |
1871 | 1870 | |
1872 | 1871 | PrimOpId op _
|
1873 | 1872 | | primOpIsDiv op
|
1874 | - , Lit divisor <- Partial.last args
|
|
1873 | + , Just (initArgs, Lit divisor) <- unsnoc args
|
|
1875 | 1874 | -- there can be 2 args (most div primops) or 3 args
|
1876 | 1875 | -- (WordQuotRem2Op), hence the use of last/init
|
1877 | - -> not (isZeroLit divisor) && all (expr_ok fun_ok primop_ok) (Partial.init args)
|
|
1876 | + -> not (isZeroLit divisor) && all (expr_ok fun_ok primop_ok) initArgs
|
|
1878 | 1877 | -- Special case for dividing operations that fail
|
1879 | 1878 | -- In general they are NOT ok-for-speculation
|
1880 | 1879 | -- (which primop_ok will catch), but they ARE OK
|
... | ... | @@ -183,7 +183,7 @@ checkUnitCycles dflags graph = processSCCs (HUG.hugSCCs graph) |
183 | 183 | |
184 | 184 | processSCCs [] = return ()
|
185 | 185 | processSCCs (AcyclicSCC _: other_sccs) = processSCCs other_sccs
|
186 | - processSCCs (CyclicSCC uids: _) = throwGhcException $ CmdLineError $ showSDoc dflags (cycle_err uids)
|
|
186 | + processSCCs (NECyclicSCC uids: _) = throwGhcException $ CmdLineError $ showSDoc dflags (cycle_err uids)
|
|
187 | 187 | |
188 | 188 | |
189 | 189 | cycle_err uids =
|
... | ... | @@ -195,8 +195,8 @@ checkUnitCycles dflags graph = processSCCs (HUG.hugSCCs graph) |
195 | 195 | (map (\uid -> text "-" <+> ppr uid <+> text "depends on") start)
|
196 | 196 | ++ [text "-" <+> ppr final]
|
197 | 197 | where
|
198 | - start = init uids
|
|
199 | - final = last uids
|
|
198 | + start = NE.init uids
|
|
199 | + final = NE.last uids
|
|
200 | 200 | |
201 | 201 | -- | Check that we don't have multiple units with the same UnitId.
|
202 | 202 | checkDuplicateUnits :: DynFlags -> [(FilePath, DynFlags)] -> Ghc ()
|
... | ... | @@ -2,8 +2,8 @@ |
2 | 2 | {-# OPTIONS_HADDOCK not-home #-}
|
3 | 3 | {-# OPTIONS_GHC -O2 #-} -- See Note [-O2 Prelude]
|
4 | 4 | |
5 | --- See Note [Proxies for head and tail]
|
|
6 | -{-# OPTIONS_GHC -Wno-unrecognised-warning-flags -Wno-x-partial #-}
|
|
5 | +-- See Note [Proxies for partial list functions]
|
|
6 | +{-# OPTIONS_GHC -Wno-x-partial #-}
|
|
7 | 7 | |
8 | 8 | -- | Custom minimal GHC "Prelude"
|
9 | 9 | --
|
... | ... | @@ -24,7 +24,7 @@ module GHC.Prelude.Basic |
24 | 24 | , bit
|
25 | 25 | , shiftL, shiftR
|
26 | 26 | , setBit, clearBit
|
27 | - , head, tail, unzip
|
|
27 | + , head, tail, init, last, unzip
|
|
28 | 28 | |
29 | 29 | , strictGenericLength
|
30 | 30 | ) where
|
... | ... | @@ -59,7 +59,7 @@ NoImplicitPrelude. There are two motivations for this: |
59 | 59 | -}
|
60 | 60 | |
61 | 61 | import qualified Prelude
|
62 | -import Prelude as X hiding ((<>), Applicative(..), Foldable(..), head, tail, unzip)
|
|
62 | +import Prelude as X hiding ((<>), Applicative(..), Foldable(..), head, tail, init, last, unzip)
|
|
63 | 63 | import Control.Applicative (Applicative(..))
|
64 | 64 | import Data.Foldable as X (Foldable (elem, foldMap, foldl, foldl', foldr, length, null, product, sum))
|
65 | 65 | import Data.Foldable1 as X hiding (head, last)
|
... | ... | @@ -118,24 +118,35 @@ setBit = \ x i -> x Bits..|. bit i |
118 | 118 | clearBit :: (Num a, Bits.Bits a) => a -> Int -> a
|
119 | 119 | clearBit = \ x i -> x Bits..&. Bits.complement (bit i)
|
120 | 120 | |
121 | -{- Note [Proxies for head and tail]
|
|
121 | +{- Note [Proxies for partial list functions]
|
|
122 | 122 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
123 | -Prelude.head and Prelude.tail have recently acquired {-# WARNING in "x-partial" #-},
|
|
123 | +Prelude.head, Prelude.tail, Prelude.init and Prelude.last
|
|
124 | +have recently acquired {-# WARNING in "x-partial" #-},
|
|
124 | 125 | but the GHC codebase uses them fairly extensively and insists on building warning-free.
|
125 | 126 | Thus, instead of adding {-# OPTIONS_GHC -Wno-x-partial #-} to every module which
|
126 | 127 | employs them, we define warning-less proxies and export them from GHC.Prelude.
|
127 | 128 | -}
|
128 | 129 | |
129 | --- See Note [Proxies for head and tail]
|
|
130 | +-- See Note [Proxies for partial list functions]
|
|
130 | 131 | head :: HasCallStack => [a] -> a
|
131 | 132 | head = Prelude.head
|
132 | 133 | {-# INLINE head #-}
|
133 | 134 | |
134 | --- See Note [Proxies for head and tail]
|
|
135 | +-- See Note [Proxies for partial list functions]
|
|
135 | 136 | tail :: HasCallStack => [a] -> [a]
|
136 | 137 | tail = Prelude.tail
|
137 | 138 | {-# INLINE tail #-}
|
138 | 139 | |
140 | +-- See Note [Proxies for partial list functions]
|
|
141 | +init :: HasCallStack => [a] -> [a]
|
|
142 | +init = Prelude.init
|
|
143 | +{-# INLINE init #-}
|
|
144 | + |
|
145 | +-- See Note [Proxies for partial list functions]
|
|
146 | +last :: HasCallStack => [a] -> a
|
|
147 | +last = Prelude.last
|
|
148 | +{-# INLINE last #-}
|
|
149 | + |
|
139 | 150 | {- |
|
140 | 151 | The 'genericLength' function defined in base can't be specialised due to the
|
141 | 152 | NOINLINE pragma.
|
... | ... | @@ -133,7 +133,7 @@ import Data.Char |
133 | 133 | import Data.Function
|
134 | 134 | import qualified Data.Foldable as Foldable
|
135 | 135 | import Data.IORef ( IORef, modifyIORef, newIORef, readIORef, writeIORef )
|
136 | -import Data.List ( find, intercalate, intersperse,
|
|
136 | +import Data.List ( find, intercalate, intersperse, unsnoc,
|
|
137 | 137 | isPrefixOf, isSuffixOf, nub, partition, sort, sortBy, (\\) )
|
138 | 138 | import qualified Data.List.NonEmpty as NE
|
139 | 139 | import qualified Data.Set as S
|
... | ... | @@ -2399,9 +2399,9 @@ setContextAfterLoad keep_ctxt (Just graph) = do |
2399 | 2399 | [] ->
|
2400 | 2400 | let graph' = flattenSCCs $ filterToposortToModules $
|
2401 | 2401 | GHC.topSortModuleGraph True (GHC.mkModuleGraph loaded_graph) Nothing
|
2402 | - in case graph' of
|
|
2403 | - [] -> setContextKeepingPackageModules keep_ctxt []
|
|
2404 | - xs -> load_this (last xs)
|
|
2402 | + in case unsnoc graph' of
|
|
2403 | + Nothing -> setContextKeepingPackageModules keep_ctxt []
|
|
2404 | + Just (_, lst) -> load_this lst
|
|
2405 | 2405 | (m:_) ->
|
2406 | 2406 | load_this m
|
2407 | 2407 | where
|
... | ... | @@ -88,7 +88,7 @@ import System.Exit |
88 | 88 | import Control.Monad
|
89 | 89 | import Control.Monad.Trans.Class
|
90 | 90 | import Control.Monad.Trans.Except (throwE, runExceptT)
|
91 | -import Data.List ( isPrefixOf, partition, intercalate )
|
|
91 | +import Data.List ( isPrefixOf, partition, intercalate, unsnoc )
|
|
92 | 92 | import Prelude
|
93 | 93 | import qualified Data.List.NonEmpty as NE
|
94 | 94 | |
... | ... | @@ -115,8 +115,7 @@ main = do |
115 | 115 | argv0 <- getArgs
|
116 | 116 | |
117 | 117 | let (minusB_args, argv1) = partition ("-B" `isPrefixOf`) argv0
|
118 | - mbMinusB | null minusB_args = Nothing
|
|
119 | - | otherwise = Just (drop 2 (last minusB_args))
|
|
118 | + mbMinusB = drop 2 . snd <$> unsnoc minusB_args
|
|
120 | 119 | |
121 | 120 | let argv2 = map (mkGeneralLocated "on the commandline") argv1
|
122 | 121 |
... | ... | @@ -13,7 +13,7 @@ import GHC.Boot.TH.PprLib |
13 | 13 | import GHC.Boot.TH.Syntax
|
14 | 14 | import Data.Word ( Word8 )
|
15 | 15 | import Data.Char ( toLower, chr )
|
16 | -import Data.List ( intersperse )
|
|
16 | +import Data.List ( intersperse, unsnoc )
|
|
17 | 17 | import GHC.Show ( showMultiLineString )
|
18 | 18 | import GHC.Lexeme( isVarSymChar )
|
19 | 19 | import Data.Ratio ( numerator, denominator )
|
... | ... | @@ -214,9 +214,10 @@ pprExp i (MDoE m ss_) = parensIf (i > noPrec) $ |
214 | 214 | pprStms [s] = ppr s
|
215 | 215 | pprStms ss = braces (semiSep ss)
|
216 | 216 | |
217 | -pprExp _ (CompE []) = text "<<Empty CompExp>>"
|
|
218 | 217 | -- This will probably break with fixity declarations - would need a ';'
|
219 | -pprExp _ (CompE ss) =
|
|
218 | +pprExp _ (CompE ss) = case unsnoc ss of
|
|
219 | + Nothing -> text "<<Empty CompExp>>"
|
|
220 | + Just (ss', s) ->
|
|
220 | 221 | if null ss'
|
221 | 222 | -- If there are no statements in a list comprehension besides the last
|
222 | 223 | -- one, we simply treat it like a normal list.
|
... | ... | @@ -225,8 +226,6 @@ pprExp _ (CompE ss) = |
225 | 226 | <+> bar
|
226 | 227 | <+> commaSep ss'
|
227 | 228 | <> text "]"
|
228 | - where s = last ss
|
|
229 | - ss' = init ss
|
|
230 | 229 | pprExp _ (ArithSeqE d) = ppr d
|
231 | 230 | pprExp _ (ListE es) = brackets (commaSep es)
|
232 | 231 | pprExp i (SigE e t) = parensIf (i > noPrec) $ pprExp sigPrec e
|
... | ... | @@ -13,6 +13,9 @@ |
13 | 13 | {-# OPTIONS_HADDOCK not-home #-}
|
14 | 14 | {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
|
15 | 15 | |
16 | +-- For init in formatRealFloatAlt
|
|
17 | +{-# OPTIONS_GHC -Wno-x-partial #-}
|
|
18 | + |
|
16 | 19 | -----------------------------------------------------------------------------
|
17 | 20 | -- |
|
18 | 21 | -- Module : GHC.Internal.Float
|
... | ... | @@ -190,12 +190,18 @@ tail :: HasCallStack => [a] -> [a] |
190 | 190 | tail (_:xs) = xs
|
191 | 191 | tail [] = errorEmptyList "tail"
|
192 | 192 | |
193 | -{-# WARNING in "x-partial" tail "This is a partial function, it throws an error on empty lists. Replace it with 'drop' 1, or use pattern matching or 'GHC.Internal.Data.List.uncons' instead. Consider refactoring to use \"Data.List.NonEmpty\"." #-}
|
|
193 | +{-# WARNING in "x-partial" tail "This is a partial function, it throws an error on empty lists. Replace it with 'drop' 1, or use pattern matching or 'Data.List.uncons' instead. Consider refactoring to use \"Data.List.NonEmpty\"." #-}
|
|
194 | 194 | |
195 | 195 | -- | \(\mathcal{O}(n)\). Extract the last element of a list, which must be
|
196 | 196 | -- finite and non-empty.
|
197 | 197 | --
|
198 | --- WARNING: This function is partial. Consider using 'unsnoc' instead.
|
|
198 | +-- To disable the warning about partiality put
|
|
199 | +-- @{-# OPTIONS_GHC -Wno-x-partial -Wno-unrecognised-warning-flags #-}@
|
|
200 | +-- at the top of the file. To disable it throughout a package put the same
|
|
201 | +-- options into @ghc-options@ section of Cabal file. To disable it in GHCi
|
|
202 | +-- put @:set -Wno-x-partial -Wno-unrecognised-warning-flags@ into @~/.ghci@
|
|
203 | +-- config file. See also the
|
|
204 | +-- [migration guide](https://github.com/haskell/core-libraries-committee/blob/main/guides/warning-for-init-and-last.md).
|
|
199 | 205 | --
|
200 | 206 | -- ==== __Examples__
|
201 | 207 | --
|
... | ... | @@ -218,10 +224,18 @@ last xs = foldl (\_ x -> x) lastError xs |
218 | 224 | lastError :: HasCallStack => a
|
219 | 225 | lastError = errorEmptyList "last"
|
220 | 226 | |
227 | +{-# WARNING in "x-partial" last "This is a partial function, it throws an error on empty lists. Use 'Data.List.unsnoc' instead. Consider refactoring to use \"Data.List.NonEmpty\"." #-}
|
|
228 | + |
|
221 | 229 | -- | \(\mathcal{O}(n)\). Return all the elements of a list except the last one.
|
222 | 230 | -- The list must be non-empty.
|
223 | 231 | --
|
224 | --- WARNING: This function is partial. Consider using 'unsnoc' instead.
|
|
232 | +-- To disable the warning about partiality put
|
|
233 | +-- @{-# OPTIONS_GHC -Wno-x-partial -Wno-unrecognised-warning-flags #-}@
|
|
234 | +-- at the top of the file. To disable it throughout a package put the same
|
|
235 | +-- options into @ghc-options@ section of Cabal file. To disable it in GHCi
|
|
236 | +-- put @:set -Wno-x-partial -Wno-unrecognised-warning-flags@ into @~/.ghci@
|
|
237 | +-- config file. See also the
|
|
238 | +-- [migration guide](https://github.com/haskell/core-libraries-committee/blob/main/guides/warning-for-init-and-last.md).
|
|
225 | 239 | --
|
226 | 240 | -- ==== __Examples__
|
227 | 241 | --
|
... | ... | @@ -240,6 +254,8 @@ init (x:xs) = init' x xs |
240 | 254 | where init' _ [] = []
|
241 | 255 | init' y (z:zs) = y : init' z zs
|
242 | 256 | |
257 | +{-# WARNING in "x-partial" init "This is a partial function, it throws an error on empty lists. Use 'Data.List.unsnoc' instead. Consider refactoring to use \"Data.List.NonEmpty\"." #-}
|
|
258 | + |
|
243 | 259 | -- | \(\mathcal{O}(1)\). Test whether a list is empty.
|
244 | 260 | --
|
245 | 261 | -- >>> null []
|
... | ... | @@ -825,11 +825,12 @@ output_flags = std_flags |
825 | 825 | |
826 | 826 | where
|
827 | 827 | -- XXX bits copied from System.FilePath, since that's not available here
|
828 | - combine a b
|
|
829 | - | null b = a
|
|
830 | - | null a = b
|
|
831 | - | pathSeparator [last a] = a ++ b
|
|
832 | - | otherwise = a ++ [pathSeparatorChar] ++ b
|
|
828 | + combine a [] = a
|
|
829 | + combine a b = case unsnoc a of
|
|
830 | + Nothing -> b
|
|
831 | + Just (_, lastA)
|
|
832 | + | pathSeparator [lastA] -> a ++ b
|
|
833 | + | otherwise -> a ++ [pathSeparatorChar] ++ b
|
|
833 | 834 | |
834 | 835 | tempCounter :: IORef Int
|
835 | 836 | tempCounter = unsafePerformIO $ newIORef 0
|
... | ... | @@ -54,6 +54,7 @@ import GHC.Internal.Base hiding (NonEmpty(..),Type, Module, sequence) |
54 | 54 | import GHC.Internal.Data.Data hiding (Fixity(..))
|
55 | 55 | import GHC.Internal.Data.NonEmpty (NonEmpty(..))
|
56 | 56 | import GHC.Internal.Data.Traversable
|
57 | +import GHC.Internal.List (unsnoc)
|
|
57 | 58 | import GHC.Internal.Word
|
58 | 59 | import GHC.Internal.Generics (Generic)
|
59 | 60 | import GHC.Internal.IORef
|
... | ... | @@ -73,7 +74,7 @@ import GHC.Internal.Control.Monad.Fix |
73 | 74 | import GHC.Internal.Control.Exception
|
74 | 75 | import GHC.Internal.Num
|
75 | 76 | import GHC.Internal.IO.Unsafe
|
76 | -import GHC.Internal.List (dropWhile, break, replicate, reverse, last)
|
|
77 | +import GHC.Internal.List (dropWhile, break, replicate, reverse)
|
|
77 | 78 | import GHC.Internal.MVar
|
78 | 79 | import GHC.Internal.IO.Exception
|
79 | 80 | import GHC.Internal.Unicode
|
... | ... | @@ -82,6 +83,16 @@ import qualified GHC.Internal.Types as Kind (Type) |
82 | 83 | import GHC.Internal.ForeignSrcLang
|
83 | 84 | import GHC.Internal.LanguageExtensions
|
84 | 85 | |
86 | +#ifdef BOOTSTRAP_TH
|
|
87 | +#if MIN_VERSION_base(4,19,0)
|
|
88 | +import Data.List (unsnoc)
|
|
89 | +#else
|
|
90 | +import Data.Maybe (maybe)
|
|
91 | +unsnoc :: [a] -> Maybe ([a], a)
|
|
92 | +unsnoc = foldr (\x -> Just . maybe ([], x) (\(~(a, b)) -> (x : a, b))) Nothing
|
|
93 | +#endif
|
|
94 | +#endif
|
|
95 | + |
|
85 | 96 | -----------------------------------------------------
|
86 | 97 | --
|
87 | 98 | -- The Quasi class
|
... | ... | @@ -1296,7 +1307,7 @@ mkName str |
1296 | 1307 | -- (i.e. non-empty, starts with capital, all alpha)
|
1297 | 1308 | is_rev_mod_name rev_mod_str
|
1298 | 1309 | | (compt, rest) <- break (== '.') rev_mod_str
|
1299 | - , not (null compt), isUpper (last compt), all is_mod_char compt
|
|
1310 | + , Just (_, lastCompt) <- unsnoc compt, isUpper lastCompt, all is_mod_char compt
|
|
1300 | 1311 | = case rest of
|
1301 | 1312 | [] -> True
|
1302 | 1313 | (_dot : rest') -> is_rev_mod_name rest'
|
... | ... | @@ -104,7 +104,7 @@ module System.FilePath.Posix |
104 | 104 | |
105 | 105 | import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper)
|
106 | 106 | import Data.Maybe(isJust)
|
107 | -import Data.List(stripPrefix, isSuffixOf)
|
|
107 | +import Data.List(stripPrefix, isSuffixOf, uncons, unsnoc)
|
|
108 | 108 | |
109 | 109 | import System.Environment(getEnv)
|
110 | 110 | |
... | ... | @@ -203,14 +203,20 @@ isExtSeparator = (== extSeparator) |
203 | 203 | splitSearchPath :: String -> [FilePath]
|
204 | 204 | splitSearchPath = f
|
205 | 205 | where
|
206 | - f xs = case break isSearchPathSeparator xs of
|
|
207 | - (pre, [] ) -> g pre
|
|
208 | - (pre, _:post) -> g pre ++ f post
|
|
209 | - |
|
210 | - g "" = ["." | isPosix]
|
|
211 | - g ('\"':x@(_:_)) | isWindows && last x == '\"' = [init x]
|
|
212 | - g x = [x]
|
|
213 | - |
|
206 | + f xs = let (pre, post) = break isSearchPathSeparator xs
|
|
207 | + in case uncons post of
|
|
208 | + Nothing -> g pre
|
|
209 | + Just (_, t) -> g pre ++ f t
|
|
210 | + |
|
211 | + g x = case uncons x of
|
|
212 | + Nothing -> ["." | isPosix]
|
|
213 | + Just (h, t)
|
|
214 | + | h == '"'
|
|
215 | + , Just{} <- uncons t -- >= 2
|
|
216 | + , isWindows
|
|
217 | + , Just (i, l) <- unsnoc t
|
|
218 | + , l == '"' -> [i]
|
|
219 | + | otherwise -> [x]
|
|
214 | 220 | |
215 | 221 | -- | Get a list of 'FilePath's in the $PATH variable.
|
216 | 222 | getSearchPath :: IO [FilePath]
|
... | ... | @@ -233,12 +239,17 @@ getSearchPath = fmap splitSearchPath (getEnv "PATH") |
233 | 239 | -- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
|
234 | 240 | -- > splitExtension "file/path.txt/" == ("file/path.txt/","")
|
235 | 241 | splitExtension :: FilePath -> (String, String)
|
236 | -splitExtension x = case nameDot of
|
|
237 | - "" -> (x,"")
|
|
238 | - _ -> (dir ++ init nameDot, extSeparator : ext)
|
|
239 | - where
|
|
240 | - (dir,file) = splitFileName_ x
|
|
241 | - (nameDot,ext) = breakEnd isExtSeparator file
|
|
242 | +splitExtension x = case unsnoc nameDot of
|
|
243 | + -- Imagine x = "no-dots", then nameDot = ""
|
|
244 | + Nothing -> (x, mempty)
|
|
245 | + Just (initNameDot, _)
|
|
246 | + -- Imagine x = "\\shared.with.dots\no-dots"
|
|
247 | + | isWindows && null (dropDrive nameDot) -> (x, mempty)
|
|
248 | + -- Imagine x = "dir.with.dots/no-dots"
|
|
249 | + | any isPathSeparator ext -> (x, mempty)
|
|
250 | + | otherwise -> (initNameDot, extSeparator : ext)
|
|
251 | + where
|
|
252 | + (nameDot, ext) = breakEnd isExtSeparator x
|
|
242 | 253 | |
243 | 254 | -- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.
|
244 | 255 | --
|
... | ... | @@ -594,9 +605,13 @@ replaceBaseName pth nam = combineAlways a (nam <.> ext) |
594 | 605 | -- > hasTrailingPathSeparator "test" == False
|
595 | 606 | -- > hasTrailingPathSeparator "test/" == True
|
596 | 607 | hasTrailingPathSeparator :: FilePath -> Bool
|
597 | -hasTrailingPathSeparator "" = False
|
|
598 | -hasTrailingPathSeparator x = isPathSeparator (last x)
|
|
608 | +hasTrailingPathSeparator = isJust . getTrailingPathSeparator
|
|
599 | 609 | |
610 | +getTrailingPathSeparator :: FilePath -> Maybe Char
|
|
611 | +getTrailingPathSeparator x = case unsnoc x of
|
|
612 | + Just (_, lastX)
|
|
613 | + | isPathSeparator lastX -> Just lastX
|
|
614 | + _ -> Nothing
|
|
600 | 615 | |
601 | 616 | hasLeadingPathSeparator :: FilePath -> Bool
|
602 | 617 | hasLeadingPathSeparator "" = False
|
... | ... | @@ -619,12 +634,12 @@ addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x ++ [pat |
619 | 634 | -- > Windows: dropTrailingPathSeparator "\\" == "\\"
|
620 | 635 | -- > Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x
|
621 | 636 | dropTrailingPathSeparator :: FilePath -> FilePath
|
622 | -dropTrailingPathSeparator x =
|
|
623 | - if hasTrailingPathSeparator x && not (isDrive x)
|
|
624 | - then let x' = dropWhileEnd isPathSeparator x
|
|
625 | - in if null x' then [last x] else x'
|
|
626 | - else x
|
|
627 | - |
|
637 | +dropTrailingPathSeparator x = case getTrailingPathSeparator x of
|
|
638 | + Just lastX
|
|
639 | + | not (isDrive x)
|
|
640 | + -> let x' = dropWhileEnd isPathSeparator x
|
|
641 | + in if null x' then [lastX] else x'
|
|
642 | + _ -> x
|
|
628 | 643 | |
629 | 644 | -- | Get the directory name, move up one level.
|
630 | 645 | --
|
... | ... | @@ -863,28 +878,37 @@ makeRelative root path |
863 | 878 | -- > Posix: normalise "bob/fred/." == "bob/fred/"
|
864 | 879 | -- > Posix: normalise "//home" == "/home"
|
865 | 880 | normalise :: FilePath -> FilePath
|
866 | -normalise path = result ++ [pathSeparator | addPathSeparator]
|
|
867 | - where
|
|
868 | - (drv,pth) = splitDrive path
|
|
869 | - result = joinDrive' (normaliseDrive drv) (f pth)
|
|
881 | +normalise filepath =
|
|
882 | + result <>
|
|
883 | + (if addPathSeparator
|
|
884 | + then [pathSeparator]
|
|
885 | + else mempty)
|
|
886 | + where
|
|
887 | + (drv,pth) = splitDrive filepath
|
|
888 | + |
|
889 | + result = joinDrive' (normaliseDrive drv) (f pth)
|
|
870 | 890 | |
871 | - joinDrive' "" "" = "."
|
|
872 | - joinDrive' d p = joinDrive d p
|
|
891 | + joinDrive' d p
|
|
892 | + = if null d && null p
|
|
893 | + then "."
|
|
894 | + else joinDrive d p
|
|
873 | 895 | |
874 | - addPathSeparator = isDirPath pth
|
|
875 | - && not (hasTrailingPathSeparator result)
|
|
876 | - && not (isRelativeDrive drv)
|
|
896 | + addPathSeparator = isDirPath pth
|
|
897 | + && not (hasTrailingPathSeparator result)
|
|
898 | + && not (isRelativeDrive drv)
|
|
877 | 899 | |
878 | - isDirPath xs = hasTrailingPathSeparator xs
|
|
879 | - || not (null xs) && last xs == '.' && hasTrailingPathSeparator (init xs)
|
|
900 | + isDirPath xs = hasTrailingPathSeparator xs || case unsnoc xs of
|
|
901 | + Nothing -> False
|
|
902 | + Just (initXs, lastXs) -> lastXs == '.' && hasTrailingPathSeparator initXs
|
|
880 | 903 | |
881 | - f = joinPath . dropDots . propSep . splitDirectories
|
|
904 | + f = joinPath . dropDots . propSep . splitDirectories
|
|
882 | 905 | |
883 | - propSep (x:xs) | all isPathSeparator x = [pathSeparator] : xs
|
|
884 | - | otherwise = x : xs
|
|
885 | - propSep [] = []
|
|
906 | + propSep (x:xs)
|
|
907 | + | all isPathSeparator x = [pathSeparator] : xs
|
|
908 | + | otherwise = x : xs
|
|
909 | + propSep [] = []
|
|
886 | 910 | |
887 | - dropDots = filter ("." /=)
|
|
911 | + dropDots = filter ("." /=)
|
|
888 | 912 | |
889 | 913 | normaliseDrive :: FilePath -> FilePath
|
890 | 914 | normaliseDrive "" = ""
|
... | ... | @@ -104,7 +104,7 @@ module System.FilePath.Windows |
104 | 104 | |
105 | 105 | import Data.Char(toLower, toUpper, isAsciiLower, isAsciiUpper)
|
106 | 106 | import Data.Maybe(isJust)
|
107 | -import Data.List(stripPrefix, isSuffixOf)
|
|
107 | +import Data.List(stripPrefix, isSuffixOf, uncons, unsnoc)
|
|
108 | 108 | |
109 | 109 | import System.Environment(getEnv)
|
110 | 110 | |
... | ... | @@ -203,14 +203,20 @@ isExtSeparator = (== extSeparator) |
203 | 203 | splitSearchPath :: String -> [FilePath]
|
204 | 204 | splitSearchPath = f
|
205 | 205 | where
|
206 | - f xs = case break isSearchPathSeparator xs of
|
|
207 | - (pre, [] ) -> g pre
|
|
208 | - (pre, _:post) -> g pre ++ f post
|
|
209 | - |
|
210 | - g "" = ["." | isPosix]
|
|
211 | - g ('\"':x@(_:_)) | isWindows && last x == '\"' = [init x]
|
|
212 | - g x = [x]
|
|
213 | - |
|
206 | + f xs = let (pre, post) = break isSearchPathSeparator xs
|
|
207 | + in case uncons post of
|
|
208 | + Nothing -> g pre
|
|
209 | + Just (_, t) -> g pre ++ f t
|
|
210 | + |
|
211 | + g x = case uncons x of
|
|
212 | + Nothing -> ["." | isPosix]
|
|
213 | + Just (h, t)
|
|
214 | + | h == '"'
|
|
215 | + , Just{} <- uncons t -- >= 2
|
|
216 | + , isWindows
|
|
217 | + , Just (i, l) <- unsnoc t
|
|
218 | + , l == '"' -> [i]
|
|
219 | + | otherwise -> [x]
|
|
214 | 220 | |
215 | 221 | -- | Get a list of 'FilePath's in the $PATH variable.
|
216 | 222 | getSearchPath :: IO [FilePath]
|
... | ... | @@ -233,12 +239,17 @@ getSearchPath = fmap splitSearchPath (getEnv "PATH") |
233 | 239 | -- > splitExtension "file/path.txt.bob.fred" == ("file/path.txt.bob",".fred")
|
234 | 240 | -- > splitExtension "file/path.txt/" == ("file/path.txt/","")
|
235 | 241 | splitExtension :: FilePath -> (String, String)
|
236 | -splitExtension x = case nameDot of
|
|
237 | - "" -> (x,"")
|
|
238 | - _ -> (dir ++ init nameDot, extSeparator : ext)
|
|
239 | - where
|
|
240 | - (dir,file) = splitFileName_ x
|
|
241 | - (nameDot,ext) = breakEnd isExtSeparator file
|
|
242 | +splitExtension x = case unsnoc nameDot of
|
|
243 | + -- Imagine x = "no-dots", then nameDot = ""
|
|
244 | + Nothing -> (x, mempty)
|
|
245 | + Just (initNameDot, _)
|
|
246 | + -- Imagine x = "\\shared.with.dots\no-dots"
|
|
247 | + | isWindows && null (dropDrive nameDot) -> (x, mempty)
|
|
248 | + -- Imagine x = "dir.with.dots/no-dots"
|
|
249 | + | any isPathSeparator ext -> (x, mempty)
|
|
250 | + | otherwise -> (initNameDot, extSeparator : ext)
|
|
251 | + where
|
|
252 | + (nameDot, ext) = breakEnd isExtSeparator x
|
|
242 | 253 | |
243 | 254 | -- | Get the extension of a file, returns @\"\"@ for no extension, @.ext@ otherwise.
|
244 | 255 | --
|
... | ... | @@ -594,9 +605,13 @@ replaceBaseName pth nam = combineAlways a (nam <.> ext) |
594 | 605 | -- > hasTrailingPathSeparator "test" == False
|
595 | 606 | -- > hasTrailingPathSeparator "test/" == True
|
596 | 607 | hasTrailingPathSeparator :: FilePath -> Bool
|
597 | -hasTrailingPathSeparator "" = False
|
|
598 | -hasTrailingPathSeparator x = isPathSeparator (last x)
|
|
608 | +hasTrailingPathSeparator = isJust . getTrailingPathSeparator
|
|
599 | 609 | |
610 | +getTrailingPathSeparator :: FilePath -> Maybe Char
|
|
611 | +getTrailingPathSeparator x = case unsnoc x of
|
|
612 | + Just (_, lastX)
|
|
613 | + | isPathSeparator lastX -> Just lastX
|
|
614 | + _ -> Nothing
|
|
600 | 615 | |
601 | 616 | hasLeadingPathSeparator :: FilePath -> Bool
|
602 | 617 | hasLeadingPathSeparator "" = False
|
... | ... | @@ -619,12 +634,12 @@ addTrailingPathSeparator x = if hasTrailingPathSeparator x then x else x ++ [pat |
619 | 634 | -- > Windows: dropTrailingPathSeparator "\\" == "\\"
|
620 | 635 | -- > Posix: not (hasTrailingPathSeparator (dropTrailingPathSeparator x)) || isDrive x
|
621 | 636 | dropTrailingPathSeparator :: FilePath -> FilePath
|
622 | -dropTrailingPathSeparator x =
|
|
623 | - if hasTrailingPathSeparator x && not (isDrive x)
|
|
624 | - then let x' = dropWhileEnd isPathSeparator x
|
|
625 | - in if null x' then [last x] else x'
|
|
626 | - else x
|
|
627 | - |
|
637 | +dropTrailingPathSeparator x = case getTrailingPathSeparator x of
|
|
638 | + Just lastX
|
|
639 | + | not (isDrive x)
|
|
640 | + -> let x' = dropWhileEnd isPathSeparator x
|
|
641 | + in if null x' then [lastX] else x'
|
|
642 | + _ -> x
|
|
628 | 643 | |
629 | 644 | -- | Get the directory name, move up one level.
|
630 | 645 | --
|
... | ... | @@ -863,28 +878,37 @@ makeRelative root path |
863 | 878 | -- > Posix: normalise "bob/fred/." == "bob/fred/"
|
864 | 879 | -- > Posix: normalise "//home" == "/home"
|
865 | 880 | normalise :: FilePath -> FilePath
|
866 | -normalise path = result ++ [pathSeparator | addPathSeparator]
|
|
867 | - where
|
|
868 | - (drv,pth) = splitDrive path
|
|
869 | - result = joinDrive' (normaliseDrive drv) (f pth)
|
|
881 | +normalise filepath =
|
|
882 | + result <>
|
|
883 | + (if addPathSeparator
|
|
884 | + then [pathSeparator]
|
|
885 | + else mempty)
|
|
886 | + where
|
|
887 | + (drv,pth) = splitDrive filepath
|
|
888 | + |
|
889 | + result = joinDrive' (normaliseDrive drv) (f pth)
|
|
870 | 890 | |
871 | - joinDrive' "" "" = "."
|
|
872 | - joinDrive' d p = joinDrive d p
|
|
891 | + joinDrive' d p
|
|
892 | + = if null d && null p
|
|
893 | + then "."
|
|
894 | + else joinDrive d p
|
|
873 | 895 | |
874 | - addPathSeparator = isDirPath pth
|
|
875 | - && not (hasTrailingPathSeparator result)
|
|
876 | - && not (isRelativeDrive drv)
|
|
896 | + addPathSeparator = isDirPath pth
|
|
897 | + && not (hasTrailingPathSeparator result)
|
|
898 | + && not (isRelativeDrive drv)
|
|
877 | 899 | |
878 | - isDirPath xs = hasTrailingPathSeparator xs
|
|
879 | - || not (null xs) && last xs == '.' && hasTrailingPathSeparator (init xs)
|
|
900 | + isDirPath xs = hasTrailingPathSeparator xs || case unsnoc xs of
|
|
901 | + Nothing -> False
|
|
902 | + Just (initXs, lastXs) -> lastXs == '.' && hasTrailingPathSeparator initXs
|
|
880 | 903 | |
881 | - f = joinPath . dropDots . propSep . splitDirectories
|
|
904 | + f = joinPath . dropDots . propSep . splitDirectories
|
|
882 | 905 | |
883 | - propSep (x:xs) | all isPathSeparator x = [pathSeparator] : xs
|
|
884 | - | otherwise = x : xs
|
|
885 | - propSep [] = []
|
|
906 | + propSep (x:xs)
|
|
907 | + | all isPathSeparator x = [pathSeparator] : xs
|
|
908 | + | otherwise = x : xs
|
|
909 | + propSep [] = []
|
|
886 | 910 | |
887 | - dropDots = filter ("." /=)
|
|
911 | + dropDots = filter ("." /=)
|
|
888 | 912 | |
889 | 913 | normaliseDrive :: FilePath -> FilePath
|
890 | 914 | normaliseDrive "" = ""
|
... | ... | @@ -7,7 +7,7 @@ import System.Environment |
7 | 7 | import GHC.Driver.Env.Types
|
8 | 8 | import GHC.Profiling
|
9 | 9 | import System.Mem
|
10 | -import Data.List (isPrefixOf)
|
|
10 | +import Data.List (isPrefixOf, unsnoc)
|
|
11 | 11 | import Control.Monad
|
12 | 12 | import System.Exit
|
13 | 13 | import GHC.Platform
|
... | ... | @@ -41,7 +41,9 @@ initGhcM xs = do |
41 | 41 | requestHeapCensus
|
42 | 42 | performGC
|
43 | 43 | [ys] <- filter (isPrefixOf (ghcUnitId <> ":GHC.Unit.Module.ModDetails.ModDetails")) . lines <$> readFile "jspace.hp"
|
44 | - let (n :: Int) = read (last (words ys))
|
|
44 | + let (n :: Int) = case unsnoc (words ys) of
|
|
45 | + Nothing -> error "input is unexpectedly empty"
|
|
46 | + Just (_, lst) -> read lst
|
|
45 | 47 | -- The output should be 50 * 8 * word_size (i.e. 3600, or 1600 on 32-bit architectures):
|
46 | 48 | -- the test contains DEPTH + WIDTH + 2 = 50 modules J, H_0, .., H_DEPTH, W_1, .., W_WIDTH,
|
47 | 49 | -- and each ModDetails contains 1 (info table) + 8 word-sized fields.
|
1 | +{-# OPTIONS_GHC -Wno-x-partial #-}
|
|
2 | + |
|
1 | 3 | module KeepCafsBase (x) where
|
2 | 4 | |
3 | 5 | x :: Int
|
... | ... | @@ -37,7 +37,7 @@ import GHC.Base (NonEmpty(..)) |
37 | 37 | import GHC.Parser.Lexer (allocateComments)
|
38 | 38 | |
39 | 39 | import Data.Data hiding ( Fixity )
|
40 | -import Data.List (sortBy, partition)
|
|
40 | +import Data.List (sortBy, partition, unsnoc)
|
|
41 | 41 | import qualified Data.Map.Strict as Map
|
42 | 42 | |
43 | 43 | import Debug.Trace
|
... | ... | @@ -734,8 +734,9 @@ ghead info [] = error $ "ghead "++info++" []" |
734 | 734 | ghead _info (h:_) = h
|
735 | 735 | |
736 | 736 | glast :: String -> [a] -> a
|
737 | -glast info [] = error $ "glast " ++ info ++ " []"
|
|
738 | -glast _info h = last h
|
|
737 | +glast info xs = case unsnoc xs of
|
|
738 | + Nothing -> error $ "glast " ++ info ++ " []"
|
|
739 | + Just (_, lst) -> lst
|
|
739 | 740 | |
740 | 741 | gtail :: String -> [a] -> [a]
|
741 | 742 | gtail info [] = error $ "gtail " ++ info ++ " []"
|
... | ... | @@ -755,7 +755,7 @@ ppHtmlIndex |
755 | 755 | divAlphabet
|
756 | 756 | << unordList
|
757 | 757 | ( map (\str -> anchor ! [href (subIndexHtmlFile str)] << str) $
|
758 | - [ [c] | c <- initialChars, any ((== c) . toUpper . head . fst) index
|
|
758 | + [ [c] | c <- initialChars, any (maybe False ((== c) . toUpper . fst) . List.uncons . fst) index
|
|
759 | 759 | ]
|
760 | 760 | ++ [merged_name]
|
761 | 761 | )
|
... | ... | @@ -772,7 +772,7 @@ ppHtmlIndex |
772 | 772 | writeUtf8File (joinPath [odir, subIndexHtmlFile [c]]) (renderToString debug html)
|
773 | 773 | where
|
774 | 774 | html = indexPage True (Just c) index_part
|
775 | - index_part = [(n, stuff) | (n, stuff) <- this_ix, toUpper (head n) == c]
|
|
775 | + index_part = [(n, stuff) | (n@(headN : _), stuff) <- this_ix, toUpper headN == c]
|
|
776 | 776 | |
777 | 777 | index :: [(String, Map GHC.Name [(Module, Bool)])]
|
778 | 778 | index = sortBy cmp (Map.toAscList full_index)
|
... | ... | @@ -30,7 +30,7 @@ import Control.Arrow (first) |
30 | 30 | import Control.Monad
|
31 | 31 | import Data.Char (chr, isAlpha, isSpace, isUpper)
|
32 | 32 | import Data.Functor (($>))
|
33 | -import Data.List (elemIndex, intercalate, intersperse, unfoldr)
|
|
33 | +import Data.List (elemIndex, intercalate, intersperse, unfoldr, unsnoc)
|
|
34 | 34 | import Data.Maybe (fromMaybe, mapMaybe)
|
35 | 35 | import Data.Monoid
|
36 | 36 | import qualified Data.Set as Set
|
... | ... | @@ -870,10 +870,10 @@ codeblock = |
870 | 870 | DocCodeBlock . parseParagraph . dropSpaces
|
871 | 871 | <$> ("@" *> skipHorizontalSpace *> "\n" *> block' <* "@")
|
872 | 872 | where
|
873 | - dropSpaces xs =
|
|
874 | - case splitByNl xs of
|
|
875 | - [] -> xs
|
|
876 | - ys -> case T.uncons (last ys) of
|
|
873 | + dropSpaces xs = let ys = splitByNl xs in
|
|
874 | + case unsnoc ys of
|
|
875 | + Nothing -> xs
|
|
876 | + Just (_, lastYs) -> case T.uncons lastYs of
|
|
877 | 877 | Just (' ', _) -> case mapM dropSpace ys of
|
878 | 878 | Nothing -> xs
|
879 | 879 | Just zs -> T.intercalate "\n" zs
|