@@ -4,92 +4,176 @@
5-- | Manipulations on import lists.
5-- | Manipulations on import lists.
6module Ormolu.Imports
6module Ormolu.Imports
7− ( sortImports,
7+ ( normalizeImports,
11import Data.Bifunctor
11import Data.Bifunctor
12import Data.Char (isAlphaNum)
12import Data.Char (isAlphaNum)
13import Data.Function (on)
13import Data.Function (on)
14−import Data.Generics (gcompare)
15−import Data.List (nubBy, sortBy)
14+import Data.List (foldl', nubBy, sortBy, sortOn)
15+import Data.Map.Strict (Map)
16+import qualified Data.Map.Strict as M
17+import FastString (FastString)
16import GHC hiding (GhcPs, IE)
18import GHC hiding (GhcPs, IE)
17import GHC.Hs.Extension
19import GHC.Hs.Extension
18import GHC.Hs.ImpExp (IE (..))
20import GHC.Hs.ImpExp (IE (..))
19import Ormolu.Utils (notImplemented, showOutputable)
21import Ormolu.Utils (notImplemented, showOutputable)
21−-- | Sort imports by module name. This also sorts explicit import lists for
23+-- | Sort and normalize imports.
23−sortImports :: [LImportDecl GhcPs] -> [LImportDecl GhcPs]
24+normalizeImports :: [LImportDecl GhcPs] -> [LImportDecl GhcPs]
24−sortImports = sortBy compareIdecl . fmap (fmap sortImportLists)
25+normalizeImports =
28+ . M.fromListWith combineImports
29+ . fmap (\x -> (importId x, g x))
26− sortImportLists :: ImportDecl GhcPs -> ImportDecl GhcPs
27− sortImportLists = \case
28− ImportDecl {..} ->
31+ g (L l ImportDecl {..}) =
29 ImportDecl
34 ImportDecl
30− { ideclHiding = second (fmap sortLies) <$> ideclHiding,
35+ { ideclHiding = second (fmap normalizeLies) <$> ideclHiding,
33− XImportDecl x -> noExtCon x
38+ g _ = notImplemented "XImportDecl"
35−-- | Compare two @'LImportDecl' 'GhcPs'@ things.
40+-- | Combine two import declarations. It should be assumed that 'ImportId's
36−compareIdecl :: LImportDecl GhcPs -> LImportDecl GhcPs -> Ordering
43+ LImportDecl GhcPs ->
37−compareIdecl (L _ m0) (L _ m1) =
44+ LImportDecl GhcPs ->
38− case (isPrelude n0, isPrelude n1) of
45+ LImportDecl GhcPs
39− (False, False) -> n0 `compare` n1
46+combineImports (L lx ImportDecl {..}) (L _ y) =
40− (True, False) -> GT
47+ L
41− (False, True) -> LT
48+ lx
42− (True, True) -> m0 `gcompare` m1
49+ ImportDecl
43− where
50+ { ideclHiding = case (ideclHiding, GHC.ideclHiding y) of
44− n0 = unLoc (ideclName m0)
51+ (Just (hiding, L l' xs), Just (_, L _ ys)) ->
45− n1 = unLoc (ideclName m1)
52+ Just (hiding, (L l' (normalizeLies (xs ++ ys))))
46− isPrelude = (== "Prelude") . moduleNameString
53+ _ -> Nothing,
56+combineImports _ _ = notImplemented "XImportDecl"
48−-- | Sort located import or export, dropping duplicates.
58+-- | Import id, a collection of all things that justify having a separate
49−sortLies :: [LIE GhcPs] -> [LIE GhcPs]
59+-- import entry. This is used for merging of imports. If two imports have
50−sortLies =
60+-- the same 'ImportId' they can be merged.
51− nubBy (\x y -> compareLIE x y == EQ)
61+data ImportId = ImportId
52− . sortBy compareLIEAll
62+ { importIsPrelude :: Bool,
53− . fmap (fmap sortThings)
63+ importIdName :: ModuleName,
64+ importPkgQual :: Maybe FastString,
65+ importSource :: Bool,
67+ importQualified :: Bool,
68+ importImplicit :: Bool,
69+ importAs :: Maybe ModuleName,
70+ importHiding :: Maybe Bool
55−-- | Sort imports\/exports inside of 'IEThingWith'.
74+-- | Obtain an 'ImportId' for a given import.
56−sortThings :: IE GhcPs -> IE GhcPs
75+importId :: LImportDecl GhcPs -> ImportId
57−sortThings = \case
76+importId (L _ ImportDecl {..}) =
58− IEThingWith NoExtField x w xs fl ->
77+ ImportId
59− IEThingWith NoExtField x w (sortBy (compareIewn `on` unLoc) xs) fl
78+ { importIsPrelude = isPrelude,
60− other -> other
79+ importIdName = moduleName,
80+ importPkgQual = sl_fs <$> ideclPkgQual,
81+ importSource = ideclSource,
82+ importSafe = ideclSafe,
83+ importQualified = case ideclQualified of
85+ QualifiedPost -> True
86+ NotQualified -> False,
87+ importImplicit = ideclImplicit,
88+ importAs = unLoc <$> ideclAs,
89+ importHiding = fst <$> ideclHiding
92+ isPrelude = moduleNameString moduleName == "Prelude"
93+ moduleName = unLoc ideclName
94+importId _ = notImplemented "XImportDecl"
62−-- | Compare a pair of located imports or exports taking into account
96+-- | Normalize a collection of import\/export items.
64−compareLIEAll :: LIE GhcPs -> LIE GhcPs -> Ordering
97+normalizeLies :: [LIE GhcPs] -> [LIE GhcPs]
65−compareLIEAll = f `on` getIewn . unLoc
98+normalizeLies = sortOn (getIewn . unLoc) . M.elems . foldl' combine M.empty
67− f (x, xall) (y, yall) =
100+ combine ::
68− case x `compareIewn` y of
101+ Map IEWrappedNameOrd (LIE GhcPs) ->
69− EQ -> xall `compare` yall
102+ LIE GhcPs ->
70− ordering -> ordering
103+ Map IEWrappedNameOrd (LIE GhcPs)
104+ combine m (L new_l new) =
105+ let wname = getIewn new
107+ nubBy (\x y -> compareLIewn x y == EQ) . sortBy compareLIewn
109+ Nothing -> Just . L new_l $
111+ IEThingWith NoExtField n wildcard g flbl ->
112+ IEThingWith NoExtField n wildcard (normalizeWNames g) flbl
116+ IEVar NoExtField n -> IEVar NoExtField n
117+ IEThingAbs NoExtField _ -> new
118+ IEThingAll NoExtField n -> IEThingAll NoExtField n
119+ IEThingWith NoExtField n wildcard g flbl ->
121+ IEVar NoExtField _ ->
122+ error "Ormolu.Imports broken presupposition"
123+ IEThingAbs NoExtField _ ->
124+ IEThingWith NoExtField n wildcard g flbl
125+ IEThingAll NoExtField n' ->
126+ IEThingAll NoExtField n'
127+ IEThingWith NoExtField n' wildcard' g' flbl' ->
128+ let combinedWildcard =
129+ case (wildcard, wildcard') of
130+ (IEWildcard _, _) -> IEWildcard 0
131+ (_, IEWildcard _) -> IEWildcard 0
137+ (normalizeWNames (g <> g'))
139+ IEModuleContents NoExtField _ -> notImplemented "IEModuleContents"
140+ IEGroup NoExtField _ _ -> notImplemented "IEGroup"
141+ IEDoc NoExtField _ -> notImplemented "IEDoc"
142+ IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
144+ IEModuleContents NoExtField _ -> notImplemented "IEModuleContents"
145+ IEGroup NoExtField _ _ -> notImplemented "IEGroup"
146+ IEDoc NoExtField _ -> notImplemented "IEDoc"
147+ IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
150+ in M.alter alter wname m
72−-- | The same as 'compareLIEAll'.
152+-- | A wrapper for @'IEWrappedName' 'RdrName'@ that allows us to define an
73−compareLIE :: LIE GhcPs -> LIE GhcPs -> Ordering
153+-- 'Ord' instance for it.
74−compareLIE = compareIewn `on` fst . getIewn . unLoc
154+newtype IEWrappedNameOrd = IEWrappedNameOrd (IEWrappedName RdrName)
76−-- | Indication whether or not a wildcard is used by an import item.
157+instance Ord IEWrappedNameOrd where
77−data IEWild = All | NoWild
158+ compare (IEWrappedNameOrd x) (IEWrappedNameOrd y) = compareIewn x y
80-- | Project @'IEWrappedName' 'RdrName'@ from @'IE' 'GhcPs'@.
160-- | Project @'IEWrappedName' 'RdrName'@ from @'IE' 'GhcPs'@.
81−getIewn :: IE GhcPs -> (IEWrappedName RdrName, IEWild)
161+getIewn :: IE GhcPs -> IEWrappedNameOrd
82getIewn = \case
162getIewn = \case
83− IEVar NoExtField x -> (unLoc x, NoWild)
163+ IEVar NoExtField x -> IEWrappedNameOrd (unLoc x)
84− IEThingAbs NoExtField x -> (unLoc x, NoWild)
164+ IEThingAbs NoExtField x -> IEWrappedNameOrd (unLoc x)
85− IEThingAll NoExtField x -> (unLoc x, All)
165+ IEThingAll NoExtField x -> IEWrappedNameOrd (unLoc x)
86− IEThingWith NoExtField x _ _ _ -> (unLoc x, NoWild)
166+ IEThingWith NoExtField x _ _ _ -> IEWrappedNameOrd (unLoc x)
87 IEModuleContents NoExtField _ -> notImplemented "IEModuleContents"
167 IEModuleContents NoExtField _ -> notImplemented "IEModuleContents"
88 IEGroup NoExtField _ _ -> notImplemented "IEGroup"
168 IEGroup NoExtField _ _ -> notImplemented "IEGroup"
89 IEDoc NoExtField _ -> notImplemented "IEDoc"
169 IEDoc NoExtField _ -> notImplemented "IEDoc"
90 IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
170 IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
91 XIE x -> noExtCon x
171 XIE x -> noExtCon x
173+-- | Like 'compareIewn' for located wrapped names.
174+compareLIewn :: LIEWrappedName RdrName -> LIEWrappedName RdrName -> Ordering
175+compareLIewn = compareIewn `on` unLoc
93-- | Compare two @'IEWrapppedName' 'RdrName'@ things.
177-- | Compare two @'IEWrapppedName' 'RdrName'@ things.
94compareIewn :: IEWrappedName RdrName -> IEWrappedName RdrName -> Ordering
178compareIewn :: IEWrappedName RdrName -> IEWrappedName RdrName -> Ordering
95compareIewn (IEName x) (IEName y) = unLoc x `compareRdrName` unLoc y
179compareIewn (IEName x) (IEName y) = unLoc x `compareRdrName` unLoc y