Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .hlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
- ignore: {name: "Replace case with maybe", within: [Distribution.Client.InLibrary]}
- ignore: {name: "Use fmap", within: [Distribution.Client.HttpUtils, Distribution.Simple.SrcDist]}

# For lists in these rules we use single letter variables like x rather than xs,
# because "hlint considers only single-letter identifiers as rules' variables.
# Anything longer will match literally".
# SEE: https://github.com/ndmitchell/hlint/issues/1612
- group:
name: cabal-suggestions
enabled: true
Expand All @@ -46,6 +50,14 @@
lhs: fromMaybe x (Data.Map.lookup k m)
rhs: Map.findWithDefault x k m
name: Use findWithDefault
- hint:
lhs: nub x
rhs: ordNub x
name: Use ordNub
note: |
The growth rate of nub is O(n^2) and for ordNub it is O(n log n). May require adding import of either;
- Distribution.Utils.Generic from Cabal-syntax, or
- Distribution.Simple.Utils from Cabal.

- arguments:
- --ignore-glob=Cabal-syntax/src/Distribution/Fields/Lexer.hs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import qualified Data.ByteString.Char8 as BS8
import qualified Distribution.Compat.CharParsing as P
import qualified Distribution.SPDX as SPDX
import qualified Distribution.Types.Lens as L
import Distribution.Utils.Generic (ordNub)

-------------------------------------------------------------------------------
-- PackageDescription
Expand Down Expand Up @@ -922,7 +923,7 @@ _syntaxFieldNames =
sequence_
[ BS8.putStrLn $ " \\ " <> n
| n <-
nub $
ordNub $
sort $
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the sort no longer needed

Copy link
Copy Markdown
Collaborator

@Bodigrim Bodigrim May 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite: ordNub does not change the order of elements, so it's not semantically equivalent to drop sort. (I don't know whether sorting makes any difference for the purposes of this function and elsewhere, but perhaps we can leave figuring this out for another time)

mconcat
[ fieldGrammarKnownFieldList packageDescriptionFieldGrammar
Expand All @@ -948,7 +949,7 @@ _syntaxExtensions =
]
where
es =
nub $
ordNub $
sort
[ prettyShow e
| e <- [minBound .. maxBound]
Expand Down
3 changes: 2 additions & 1 deletion Cabal-syntax/src/Distribution/Types/BuildInfo.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import Distribution.Utils.Path

import Distribution.Compiler
import Distribution.ModuleName
import Distribution.Utils.Generic (ordNub)
import Language.Haskell.Extension

-- Consider refactoring into executable and library versions.
Expand Down Expand Up @@ -260,7 +261,7 @@ instance Semigroup BuildInfo where
}
where
combine field = field a `mappend` field b
combineNub field = nub (combine field)
combineNub field = ordNub (combine field)
combineMby field = field b `mplus` field a

emptyBuildInfo :: BuildInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Distribution.Compat.Prelude
import Prelude ()

import Distribution.PackageDescription.Check
import Distribution.Simple.Utils (ordNub)

import Test.Tasty
import Test.Tasty.HUnit
Expand All @@ -26,7 +27,7 @@ tests =
allExplanationIdStrings = map ppCheckExplanationId [minBound..maxBound]

uniqueNames :: Bool
uniqueNames = length allExplanationIdStrings == length (nub allExplanationIdStrings)
uniqueNames = length allExplanationIdStrings == length (ordNub allExplanationIdStrings)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a regression, but this is wasteful. Would be nice to have a helper areUnique :: Ord a => [a] -> Bool, there is at least one more use case for it above.

Copy link
Copy Markdown
Collaborator Author

@philderbeast philderbeast May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please be more specific?

-- Like `uniqueNames` in the way it is implemented but taking a `[a]`.
areUnique :: Ord a => [a] -> Bool
areUnique xs = length xs == length (ordNub xs)

I went looking in this repository for other [a] -> Bool helper functions and there are a few, including these ones with the same signature:

hasNoDups :: Ord a => [a] -> Bool
hasNoDups = loop Set.empty
where
loop _ [] = True
loop s (x:xs) | s' <- Set.insert x s, Set.size s' > Set.size s
= loop s' xs
| otherwise
= False

listUnique :: Ord a => [a] -> Bool
listUnique xs =
let sorted = sort xs
in nub sorted == xs

isSorted :: Ord a => [a] -> Bool
isSorted (x1 : xs@(x2 : _)) = x1 <= x2 && isSorted xs
isSorted _ = True

Copy link
Copy Markdown
Collaborator Author

@philderbeast philderbeast May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also found these two doing the same thing, the opposite of all items being unique, all items being duplicates:

allEqual :: Eq a => [a] -> Bool
allEqual xs = length (nub xs) == 1

allEqual :: Eq a => [a] -> Bool
allEqual [] = True
allEqual [_] = True
allEqual (x:y:ys) = x == y && allEqual (y:ys)

Other implementations found hoogling:

allEqual :: Eq a => [a] -> Bool
allEqual []       = True
allEqual (x : xs) = all (== x) xs
allSame :: Eq a => [a] -> Bool
allSame [] = True
allSame (x:xs) = all (x ==) xs

Copy link
Copy Markdown
Collaborator

@Bodigrim Bodigrim May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean that areUnique xs = length xs == length (ordNub xs) is a very expensive implementation. It is better than the one using nub, but still not great. You would expect such function to return False as soon as it meets the first non-unique element, but the implementation has to build ordNub xs in full until being able to compare lengths.

It should be something like

isUnique :: [a] -> Bool 
isUnique = go S.empty 
  where 
    go !_ [] = True 
    go seen (x : xs) = if S.member x seen then False else go (S.insert x seen) xs

allEqual expressed via nub also has bad performance, it should be like Agda's implementation.

Up to you whether you want to fix these things in this PR, it's indeed a bit of scope creep.


longerThan :: [CheckExplanationIDString]
longerThan = filter ((>25). length) allExplanationIdStrings
Expand Down
9 changes: 5 additions & 4 deletions Cabal-tests/tests/UnitTests/Distribution/Utils/NubList.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Prelude ()
import Distribution.Compat.Prelude.Internal

import Distribution.Utils.NubList
import Distribution.Simple.Utils (ordNub)
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
Expand Down Expand Up @@ -46,25 +47,25 @@ prop_Ordering :: [Int] -> Property
prop_Ordering xs =
mempty <> toNubList xs' === toNubList xs' <> mempty
where
xs' = nub xs
xs' = ordNub xs

prop_DeDupe :: [Int] -> Property
prop_DeDupe xs =
fromNubList (toNubList (xs' ++ xs)) === xs' -- Note, we append primeless xs
where
xs' = nub xs
xs' = ordNub xs

prop_DeDupeR :: [Int] -> Property
prop_DeDupeR xs =
fromNubListR (toNubListR (xs ++ xs')) === xs' -- Note, we prepend primeless xs
where
xs' = nub xs
xs' = ordNub xs

prop_Nub :: [Int] -> Property
prop_Nub xs = rhs === lhs
where
rhs = fromNubList (toNubList xs)
lhs = nub xs
lhs = ordNub xs

prop_Identity :: [Int] -> Bool
prop_Identity xs =
Expand Down
3 changes: 2 additions & 1 deletion Cabal/src/Distribution/PackageDescription/Check/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Distribution.Compat.NonEmptySet (toNonEmpty)
import Distribution.Package
import Distribution.PackageDescription
import Distribution.PackageDescription.Check.Monad
import Distribution.Simple.Utils (ordNub)
import Distribution.Utils.Generic (isAscii)
import Distribution.Version

Expand Down Expand Up @@ -80,7 +81,7 @@ partitionDeps ads ns ds = do
-- shared targets that match
fads = filter (flip elem dqs . fst) ads
-- the names of such targets
inName = nub $ map fst fads :: [UnqualComponentName]
inName = ordNub $ map fst fads :: [UnqualComponentName]
-- the dependencies of such targets
inDep = concatMap snd fads :: [Dependency]

Expand Down
4 changes: 2 additions & 2 deletions Cabal/src/Distribution/PackageDescription/Check/Target.hs
Original file line number Diff line number Diff line change
Expand Up @@ -557,8 +557,8 @@ checkBuildInfoFeatures bi sv = do
checkBuildInfoExtensions :: Monad m => BuildInfo -> CheckM m ()
checkBuildInfoExtensions bi = do
let exts = allExtensions bi
extCabal1_2 = nub $ filter (`elem` compatExtensionsExtra) exts
extCabal1_4 = nub $ filter (`notElem` compatExtensions) exts
extCabal1_2 = ordNub $ filter (`elem` compatExtensionsExtra) exts
extCabal1_4 = ordNub $ filter (`notElem` compatExtensions) exts
-- As of Cabal-1.4 we can add new extensions without worrying
-- about breaking old versions of cabal.
checkSpecVer
Expand Down
4 changes: 2 additions & 2 deletions Cabal/src/Distribution/Simple.hs
Original file line number Diff line number Diff line change
Expand Up @@ -824,8 +824,8 @@ sanityCheckHookedBuildInfo verbosity pkg_descr (_, hookExes)
| exe1 : _ <- nonExistent =
dieWithException verbosity $ SanityCheckHookedBuildInfo exe1
where
pkgExeNames = nub (map exeName (executables pkg_descr))
hookExeNames = nub (map fst hookExes)
pkgExeNames = ordNub (map exeName (executables pkg_descr))
hookExeNames = ordNub (map fst hookExes)
nonExistent = hookExeNames \\ pkgExeNames
sanityCheckHookedBuildInfo _ _ _ = return ()

Expand Down
18 changes: 9 additions & 9 deletions Cabal/src/Distribution/Simple/BuildTarget.hs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ data BuildTarget
BuildTargetModule ComponentName ModuleName
| -- | A specific file within a specific component.
BuildTargetFile ComponentName FilePath
deriving (Eq, Show, Generic)
deriving (Eq, Ord, Show, Generic)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this deserves @since. Same below.


instance Binary BuildTarget

Expand Down Expand Up @@ -853,7 +853,7 @@ type Confidence = Int
data MatchError
= MatchErrorExpected String String
| MatchErrorNoSuch String String
deriving (Show, Eq)
deriving (Show, Eq, Ord)

instance Alternative Match where
empty = mzero
Expand Down Expand Up @@ -943,13 +943,13 @@ increaseConfidence = ExactMatch 1 [()]
increaseConfidenceFor :: Match a -> Match a
increaseConfidenceFor m = m >>= \r -> increaseConfidence >> return r

nubMatches :: Eq a => Match a -> Match a
nubMatches :: Ord a => Match a -> Match a
nubMatches (NoMatch d msgs) = NoMatch d msgs
nubMatches (ExactMatch d xs) = ExactMatch d (nub xs)
nubMatches (InexactMatch d xs) = InexactMatch d (nub xs)
nubMatches (ExactMatch d xs) = ExactMatch d (ordNub xs)
nubMatches (InexactMatch d xs) = InexactMatch d (ordNub xs)

nubMatchErrors :: Match a -> Match a
nubMatchErrors (NoMatch d msgs) = NoMatch d (nub msgs)
nubMatchErrors (NoMatch d msgs) = NoMatch d (ordNub msgs)
nubMatchErrors (ExactMatch d xs) = ExactMatch d xs
nubMatchErrors (InexactMatch d xs) = InexactMatch d xs

Expand All @@ -970,14 +970,14 @@ tryEach = exactMatches
-- | Given a matcher and a key to look up, use the matcher to find all the
-- possible matches. There may be 'None', a single 'Unambiguous' match or
-- you may have an 'Ambiguous' match with several possibilities.
findMatch :: Eq b => Match b -> MaybeAmbiguous b
findMatch :: Ord b => Match b -> MaybeAmbiguous b
findMatch match =
case match of
NoMatch _ msgs -> None (nub msgs)
NoMatch _ msgs -> None (ordNub msgs)
ExactMatch _ xs -> checkAmbiguous xs
InexactMatch _ xs -> checkAmbiguous xs
where
checkAmbiguous xs = case nub xs of
checkAmbiguous xs = case ordNub xs of
[x] -> Unambiguous x
xs' -> Ambiguous xs'

Expand Down
6 changes: 3 additions & 3 deletions Cabal/src/Distribution/Simple/Configure.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1369,14 +1369,14 @@ finalCheckPackage
-- Check languages and extensions
-- TODO: Move this into a helper function.
let langlist =
nub $
ordNub $
mapMaybe defaultLanguage (enabledBuildInfos pkg_descr enabled)
let langs = unsupportedLanguages comp langlist
unless (null langs) $
dieWithException verbosity $
UnsupportedLanguages (packageId pkg_descr) (compilerId comp) (map prettyShow langs)
let extlist =
nub $
ordNub $
concatMap
allExtensions
(enabledBuildInfos pkg_descr enabled)
Expand Down Expand Up @@ -2607,7 +2607,7 @@ configurePkgconfigPackages verbosity pkg_descr progdb enabled
pkgconfigBuildInfo :: [PkgconfigDependency] -> IO BuildInfo
pkgconfigBuildInfo [] = return mempty
pkgconfigBuildInfo pkgdeps = do
let pkgs = nub [prettyShow pkg | PkgconfigDependency pkg _ <- pkgdeps]
let pkgs = ordNub [prettyShow pkg | PkgconfigDependency pkg _ <- pkgdeps]
ccflags <- pkgconfig ("--cflags" : pkgs)
ldflags <- pkgconfig ("--libs" : pkgs)
ldflags_static <- pkgconfig ("--libs" : "--static" : pkgs)
Expand Down
4 changes: 2 additions & 2 deletions Cabal/src/Distribution/Simple/Register.hs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ generateOne verbHandles pkg lib lbi clbi regFlags =
-- registering into a totally different db stack can
-- fail if dependencies cannot be satisfied.
packageDbs =
nub $
ordNub $
withPackageDB lbi
++ maybeToList (flagToMaybe (regPackageDB regFlags))
distPref = fromFlag $ setupDistPref common
Expand Down Expand Up @@ -228,7 +228,7 @@ registerAll verbHandles pkg lbi regFlags ipis =
-- registering into a totally different db stack can
-- fail if dependencies cannot be satisfied.
packageDbs =
nub $
ordNub $
withPackageDB lbi
++ maybeToList (flagToMaybe (regPackageDB regFlags))
common = registerCommonFlags regFlags
Expand Down
2 changes: 1 addition & 1 deletion Cabal/src/Distribution/Simple/SetupHooks/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo a
let
(ruleGraph, ruleFromVertex, vertexFromRuleId) =
Graph.graphFromEdges
[ (rule, rId, nub $ mapMaybe directRuleDependencyMaybe allDeps)
[ (rule, rId, ordNub $ mapMaybe directRuleDependencyMaybe allDeps)
| (rId, rule) <- Map.toList allRules
, let dynDeps = maybe [] fst (Map.lookup rId dynDepsEdges)
allDeps = staticDependencies rule ++ dynDeps
Expand Down
2 changes: 1 addition & 1 deletion Cabal/src/Distribution/Simple/Test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ test args verbHandles pkg_descr lbi0 flags = do
-- Now, we get the path to the HPC artifacts and exposed modules of each
-- library by querying the package database keyed by unit-id:
let coverageFor =
nub $
ordNub $
fromFlagOrDefault [] (configCoverageFor (configFlags lbi))
<> extraCoverageFor lbi
ipkginfos <- getInstalledPackagesById verbosity lbi MissingCoveredInstalledLibrary coverageFor
Expand Down
4 changes: 2 additions & 2 deletions Cabal/src/Distribution/Simple/UHC.hs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ getInstalledPackages verbosity comp mbWorkDir packagedbs progdb = do
let compilerid = compilerId comp
systemPkgDir <- getGlobalPackageDir verbosity progdb
userPkgDir <- getUserPackageDir
let pkgDirs = nub (concatMap (packageDbPaths userPkgDir systemPkgDir mbWorkDir) packagedbs)
let pkgDirs = ordNub (concatMap (packageDbPaths userPkgDir systemPkgDir mbWorkDir) packagedbs)
-- putStrLn $ "pkgdirs: " ++ show pkgDirs
pkgs <-
map addBuiltinVersions . concat
Expand Down Expand Up @@ -289,7 +289,7 @@ constructUHCCmdLine user system lbi bi clbi odir verbosity =
++ ["--package=" ++ prettyShow (mungedName pkgid) | (_, pkgid) <- componentPackageDeps clbi]
-- search paths
++ ["-i" ++ u odir]
++ ["-i" ++ u l | l <- nub (hsSourceDirs bi)]
++ ["-i" ++ u l | l <- ordNub (hsSourceDirs bi)]
++ ["-i" ++ u (autogenComponentModulesDir lbi clbi)]
++ ["-i" ++ u (autogenPackageModulesDir lbi)]
-- cpp options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Distribution.Solver.Modular.Dependency
import Distribution.Solver.Modular.Flag
import Distribution.Solver.Modular.LabeledGraph
import Distribution.Solver.Modular.Package
import Distribution.Simple.Utils (ordNub)

-- | A (partial) package assignment. Qualified package names
-- are associated with instances.
Expand All @@ -53,7 +54,7 @@ toCPs (A pa fa sa) rdm =
vm :: Vertex -> ((), QPN, [(Component, QPN)])
cvm :: QPN -> Maybe Vertex
-- Note that the RevDepMap contains duplicate dependencies. Therefore the nub.
(g, vm, cvm) = graphFromEdges (L.map (\ (x, xs) -> ((), x, nub xs))
(g, vm, cvm) = graphFromEdges (L.map (\ (x, xs) -> ((), x, ordNub xs))
(M.toList rdm))
tg :: Graph Component
tg = transposeG g
Expand Down
3 changes: 2 additions & 1 deletion cabal-install/src/Distribution/Client/CmdSdist.hs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ import Distribution.Simple.SrcDist
import Distribution.Simple.Utils
( dieWithException
, notice
, ordNub
, withOutputMarker
, wrapText
)
Expand Down Expand Up @@ -364,7 +365,7 @@ packageToSdist verbosity projectRootDir format outputFile pkg = do
gpd = srcpkgDescription pkg

files' <- listPackageSourcesWithDie verbosity dieWithException (Just $ makeSymbolicPath dir) (flattenPackageDescription gpd) knownSuffixHandlers
let files = nub $ sort $ map (normalise . getSymbolicPath) files'
let files = ordNub $ sort $ map (normalise . getSymbolicPath) files'
let prefix = makeRelative (normalise projectRootDir) dir
write $ concat [prefix </> i ++ [nulSep] | i <- files]
TarGzArchive -> do
Expand Down
3 changes: 2 additions & 1 deletion cabal-install/src/Distribution/Client/Dependency.hs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ import Distribution.Verbosity
)
import Distribution.Version

import Distribution.Simple.Utils (ordNub)
import Distribution.Solver.Types.ComponentDeps (ComponentDeps)
import qualified Distribution.Solver.Types.ComponentDeps as CD
import Distribution.Solver.Types.ConstraintSource
Expand Down Expand Up @@ -965,7 +966,7 @@ interpretPackagesPreference selected defaultPref prefs =
Map.findWithDefault [] pkgname stanzasPrefs
stanzasPrefs =
Map.fromListWith
(\a b -> nub (a ++ b))
(\a b -> ordNub (a ++ b))
[ (pkgname, pref)
| PackageStanzasPreference pkgname pref <- prefs
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import Distribution.Client.Init.FlagExtractors (getCabalVersionNoPrompt)
import Distribution.Client.Init.Types
import Distribution.Client.Init.Utils
import Distribution.FieldGrammar.Newtypes
import Distribution.Simple.Utils (ordNub)
import Distribution.Types.PackageName (PackageName)
import Distribution.Version
import System.FilePath
Expand Down Expand Up @@ -132,7 +133,7 @@ guessApplicationDirectories flags = do
let candidates = [defaultApplicationDir, "app", "src-exe"]
in return $ case [y | x <- candidates, y <- pkgDirsContents, x == y] of
[] -> [defaultApplicationDir]
x -> map (</> pkgDirs) . nub $ x
x -> map (</> pkgDirs) (ordNub x)

-- | Try to guess the source directories, using a default value as fallback.
guessSourceDirectories :: Interactive m => InitFlags -> m [FilePath]
Expand Down
3 changes: 2 additions & 1 deletion cabal-install/src/Distribution/Client/Install.hs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ import Distribution.Utils.Path hiding
import Distribution.Simple.Utils
( VerboseException
, createDirectoryIfMissingVerbose
, ordNub
, writeFileAtomic
)
import Distribution.Simple.Utils as Utils
Expand Down Expand Up @@ -712,7 +713,7 @@ pruneInstallPlan pkgSpecifiers =
++ "required by a dependency of one of the other targets."
where
pkgids =
nub
ordNub
[ depid
| SolverInstallPlan.PackageMissingDeps _ depids <- problems
, depid <- depids
Expand Down
Loading
Loading