ormolu-0.7.3.0: src/Ormolu/Imports.hs
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
-- | Manipulations on import lists.
module Ormolu.Imports
( normalizeImports,
)
where
import Data.Bifunctor
import Data.Char (isAlphaNum)
import Data.Function (on)
import Data.List (foldl', nubBy, sortBy, sortOn)
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as M
import GHC.Data.FastString
import GHC.Hs
import GHC.Hs.ImpExp as GHC
import GHC.Types.Name.Reader
import GHC.Types.PkgQual
import GHC.Types.SourceText
import GHC.Types.SrcLoc
import Ormolu.Utils (notImplemented, showOutputable)
-- | Sort and normalize imports.
normalizeImports :: [LImportDecl GhcPs] -> [LImportDecl GhcPs]
normalizeImports =
fmap snd
. M.toAscList
. M.fromListWith combineImports
. fmap (\x -> (importId x, g x))
where
g :: LImportDecl GhcPs -> LImportDecl GhcPs
g (L l ImportDecl {..}) =
L
l
ImportDecl
{ ideclImportList = second (fmap normalizeLies) <$> ideclImportList,
..
}
-- | Combine two import declarations. It should be assumed that 'ImportId's
-- are equal.
combineImports ::
LImportDecl GhcPs ->
LImportDecl GhcPs ->
LImportDecl GhcPs
combineImports (L lx ImportDecl {..}) (L _ y) =
L
lx
ImportDecl
{ ideclImportList = case (ideclImportList, GHC.ideclImportList y) of
(Just (hiding, L l' xs), Just (_, L _ ys)) ->
Just (hiding, (L l' (normalizeLies (xs ++ ys))))
_ -> Nothing,
..
}
-- | Import id, a collection of all things that justify having a separate
-- import entry. This is used for merging of imports. If two imports have
-- the same 'ImportId' they can be merged.
data ImportId = ImportId
{ importIsPrelude :: Bool,
importPkgQual :: ImportPkgQual,
importIdName :: ModuleName,
importSource :: IsBootInterface,
importSafe :: Bool,
importQualified :: Bool,
importAs :: Maybe ModuleName,
importHiding :: Maybe ImportListInterpretationOrd
}
deriving (Eq, Ord)
data ImportPkgQual
= -- | The import is not qualified by a package name.
NoImportPkgQual
| -- | The import is qualified by an external package name.
ImportPkgQual LexicalFastString
| -- | The import is qualified by the current package being built, using the
-- special @this@ package name.
ImportPkgQualThis
deriving stock (Eq, Ord)
mkImportPkgQual :: RawPkgQual -> ImportPkgQual
mkImportPkgQual = \case
NoRawPkgQual -> NoImportPkgQual
RawPkgQual (sl_fs -> fs)
| fs == mkFastString "this" -> ImportPkgQualThis
| otherwise -> ImportPkgQual (LexicalFastString fs)
-- | 'ImportListInterpretation' does not have an 'Ord' instance.
newtype ImportListInterpretationOrd = ImportListInterpretationOrd
{ unImportListInterpretationOrd :: ImportListInterpretation
}
deriving stock (Eq)
instance Ord ImportListInterpretationOrd where
compare = compare `on` toBool . unImportListInterpretationOrd
where
toBool Exactly = False
toBool EverythingBut = True
-- | Obtain an 'ImportId' for a given import.
importId :: LImportDecl GhcPs -> ImportId
importId (L _ ImportDecl {..}) =
ImportId
{ importIsPrelude = isPrelude,
importIdName = moduleName,
importPkgQual = mkImportPkgQual ideclPkgQual,
importSource = ideclSource,
importSafe = ideclSafe,
importQualified = case ideclQualified of
QualifiedPre -> True
QualifiedPost -> True
NotQualified -> False,
importAs = unLoc <$> ideclAs,
importHiding = ImportListInterpretationOrd . fst <$> ideclImportList
}
where
isPrelude = moduleNameString moduleName == "Prelude"
moduleName = unLoc ideclName
-- | Normalize a collection of import items.
normalizeLies :: [LIE GhcPs] -> [LIE GhcPs]
normalizeLies = sortOn (getIewn . unLoc) . M.elems . foldl' combine M.empty
where
combine ::
Map IEWrappedNameOrd (LIE GhcPs) ->
LIE GhcPs ->
Map IEWrappedNameOrd (LIE GhcPs)
combine m (L new_l new) =
let wname = getIewn new
normalizeWNames =
nubBy (\x y -> compareLIewn x y == EQ) . sortBy compareLIewn
alter = \case
Nothing -> Just . L new_l $
case new of
IEThingWith _ n wildcard g ->
IEThingWith (Nothing, EpAnnNotUsed) n wildcard (normalizeWNames g)
other -> other
Just old ->
let f = \case
IEVar _ n -> IEVar Nothing n
IEThingAbs _ _ -> new
IEThingAll _ n -> IEThingAll (Nothing, EpAnnNotUsed) n
IEThingWith _ n wildcard g ->
case new of
IEVar _ _ ->
error "Ormolu.Imports broken presupposition"
IEThingAbs _ _ ->
IEThingWith (Nothing, EpAnnNotUsed) n wildcard g
IEThingAll _ n' ->
IEThingAll (Nothing, EpAnnNotUsed) n'
IEThingWith _ n' wildcard' g' ->
let combinedWildcard =
case (wildcard, wildcard') of
(IEWildcard _, _) -> IEWildcard 0
(_, IEWildcard _) -> IEWildcard 0
_ -> NoIEWildcard
in IEThingWith
(Nothing, EpAnnNotUsed)
n'
combinedWildcard
(normalizeWNames (g <> g'))
IEModuleContents _ _ -> notImplemented "IEModuleContents"
IEGroup NoExtField _ _ -> notImplemented "IEGroup"
IEDoc NoExtField _ -> notImplemented "IEDoc"
IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
IEModuleContents _ _ -> notImplemented "IEModuleContents"
IEGroup NoExtField _ _ -> notImplemented "IEGroup"
IEDoc NoExtField _ -> notImplemented "IEDoc"
IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
in Just (f <$> old)
in M.alter alter wname m
-- | A wrapper for @'IEWrappedName' 'GhcPs'@ that allows us to define an
-- 'Ord' instance for it.
newtype IEWrappedNameOrd = IEWrappedNameOrd (IEWrappedName GhcPs)
deriving (Eq)
instance Ord IEWrappedNameOrd where
compare (IEWrappedNameOrd x) (IEWrappedNameOrd y) = compareIewn x y
-- | Project @'IEWrappedName' 'GhcPs'@ from @'IE' 'GhcPs'@.
getIewn :: IE GhcPs -> IEWrappedNameOrd
getIewn = \case
IEVar _ x -> IEWrappedNameOrd (unLoc x)
IEThingAbs _ x -> IEWrappedNameOrd (unLoc x)
IEThingAll _ x -> IEWrappedNameOrd (unLoc x)
IEThingWith _ x _ _ -> IEWrappedNameOrd (unLoc x)
IEModuleContents _ _ -> notImplemented "IEModuleContents"
IEGroup NoExtField _ _ -> notImplemented "IEGroup"
IEDoc NoExtField _ -> notImplemented "IEDoc"
IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
-- | Like 'compareIewn' for located wrapped names.
compareLIewn :: LIEWrappedName GhcPs -> LIEWrappedName GhcPs -> Ordering
compareLIewn = compareIewn `on` unLoc
-- | Compare two @'IEWrapppedName' 'GhcPs'@ things.
compareIewn :: IEWrappedName GhcPs -> IEWrappedName GhcPs -> Ordering
compareIewn (IEName _ x) (IEName _ y) = unLoc x `compareRdrName` unLoc y
compareIewn (IEName _ _) (IEPattern _ _) = LT
compareIewn (IEName _ _) (IEType _ _) = LT
compareIewn (IEPattern _ _) (IEName _ _) = GT
compareIewn (IEPattern _ x) (IEPattern _ y) = unLoc x `compareRdrName` unLoc y
compareIewn (IEPattern _ _) (IEType _ _) = LT
compareIewn (IEType _ _) (IEName _ _) = GT
compareIewn (IEType _ _) (IEPattern _ _) = GT
compareIewn (IEType _ x) (IEType _ y) = unLoc x `compareRdrName` unLoc y
compareRdrName :: RdrName -> RdrName -> Ordering
compareRdrName x y =
case (getNameStr x, getNameStr y) of
([], []) -> EQ
((_ : _), []) -> GT
([], (_ : _)) -> LT
((x' : _), (y' : _)) ->
case (isAlphaNum x', isAlphaNum y') of
(False, False) -> x `compare` y
(True, False) -> LT
(False, True) -> GT
(True, True) -> x `compare` y
where
getNameStr = showOutputable . rdrNameOcc