From ca83f15869cf8dd2ea5666f63fceb73df4160781 Mon Sep 17 00:00:00 2001 From: Mark Karpov Date: Mon, 22 Jun 2020 12:25:59 +0200 Subject: [PATCH] Implement merging on imports --- CHANGELOG.md | 3 + data/examples/import/merging-0-out.hs | 3 + data/examples/import/merging-0.hs | 4 + data/examples/import/merging-1-out.hs | 2 + data/examples/import/merging-1.hs | 3 + data/examples/import/merging-2-out.hs | 2 + data/examples/import/merging-2.hs | 4 + data/examples/import/qualified-post-out.hs | 2 +- data/examples/import/qualified-prelude-out.hs | 2 +- data/examples/import/simple-out.hs | 9 +- expected-failures/leksah.txt | 2 +- src/Ormolu/Diff.hs | 6 +- src/Ormolu/Imports.hs | 88 ++++++++++++++----- src/Ormolu/Printer/Meat/Module.hs | 4 +- src/Ormolu/Processing/Preprocess.hs | 3 +- src/Ormolu/Utils.hs | 2 +- 16 files changed, 101 insertions(+), 38 deletions(-) create mode 100644 data/examples/import/merging-0-out.hs create mode 100644 data/examples/import/merging-0.hs create mode 100644 data/examples/import/merging-1-out.hs create mode 100644 data/examples/import/merging-1.hs create mode 100644 data/examples/import/merging-2-out.hs create mode 100644 data/examples/import/merging-2.hs diff --git a/CHANGELOG.md b/CHANGELOG.md index c95af13..95ffa0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ * Imports in a import lists are now normalized: duplicate imports are combined/eliminated intelligently. +* Import declarations that can be merged are now automatically merged. + [Issue 414](https://github.com/tweag/ormolu/issues/414). + * The magic comments for disabling and enabling Ormolu now can encompass any fragment of code provided that the remaining code after exclusion of the disabled part is still syntactically correct. [Issue diff --git a/data/examples/import/merging-0-out.hs b/data/examples/import/merging-0-out.hs new file mode 100644 index 0000000..e96e939 --- /dev/null +++ b/data/examples/import/merging-0-out.hs @@ -0,0 +1,3 @@ +import Foo +import Foo (bar, foo) +import Foo as F diff --git a/data/examples/import/merging-0.hs b/data/examples/import/merging-0.hs new file mode 100644 index 0000000..90acbe6 --- /dev/null +++ b/data/examples/import/merging-0.hs @@ -0,0 +1,4 @@ +import Foo +import Foo (foo) +import Foo (bar) +import Foo as F diff --git a/data/examples/import/merging-1-out.hs b/data/examples/import/merging-1-out.hs new file mode 100644 index 0000000..bccdbe2 --- /dev/null +++ b/data/examples/import/merging-1-out.hs @@ -0,0 +1,2 @@ +import "bar" Foo (bar) +import "foo" Foo (baz, foo) diff --git a/data/examples/import/merging-1.hs b/data/examples/import/merging-1.hs new file mode 100644 index 0000000..d1d3fde --- /dev/null +++ b/data/examples/import/merging-1.hs @@ -0,0 +1,3 @@ +import "foo" Foo (foo) +import "bar" Foo (bar) +import "foo" Foo (baz) diff --git a/data/examples/import/merging-2-out.hs b/data/examples/import/merging-2-out.hs new file mode 100644 index 0000000..f5cc407 --- /dev/null +++ b/data/examples/import/merging-2-out.hs @@ -0,0 +1,2 @@ +import Foo hiding (bar4, foo2) +import qualified Foo (bar3, foo1) diff --git a/data/examples/import/merging-2.hs b/data/examples/import/merging-2.hs new file mode 100644 index 0000000..fdddf7f --- /dev/null +++ b/data/examples/import/merging-2.hs @@ -0,0 +1,4 @@ +import qualified Foo (foo1) +import Foo hiding (foo2) +import qualified Foo (bar3) +import Foo hiding (bar4) diff --git a/data/examples/import/qualified-post-out.hs b/data/examples/import/qualified-post-out.hs index bc299b9..265245d 100644 --- a/data/examples/import/qualified-post-out.hs +++ b/data/examples/import/qualified-post-out.hs @@ -1,5 +1,5 @@ {-# LANGUAGE ImportQualifiedPost #-} -import Data.Text qualified as T import Data.Text qualified (a, b, c) import Data.Text qualified hiding (a, b, c) +import Data.Text qualified as T diff --git a/data/examples/import/qualified-prelude-out.hs b/data/examples/import/qualified-prelude-out.hs index 7f57156..6d7aa93 100644 --- a/data/examples/import/qualified-prelude-out.hs +++ b/data/examples/import/qualified-prelude-out.hs @@ -1,4 +1,4 @@ module P where -import qualified Prelude import Prelude hiding (id, (.)) +import qualified Prelude diff --git a/data/examples/import/simple-out.hs b/data/examples/import/simple-out.hs index f5cae91..28e2a88 100644 --- a/data/examples/import/simple-out.hs +++ b/data/examples/import/simple-out.hs @@ -1,8 +1,5 @@ import Data.Text -import Data.Text -import qualified Data.Text as T +import Data.Text (a, b, c) +import Data.Text hiding (a, b, c) import qualified Data.Text (a, b, c) -import Data.Text (a, b, c) -import Data.Text hiding (a, b, c) -import Data.Text (a, b, c) -import Data.Text hiding (a, b, c) +import qualified Data.Text as T diff --git a/expected-failures/leksah.txt b/expected-failures/leksah.txt index 949a4d4..881d176 100644 --- a/expected-failures/leksah.txt +++ b/expected-failures/leksah.txt @@ -1,5 +1,5 @@ Formatting is not idempotent: - src/IDE/Pane/Modules.hs:1189:7 + src/IDE/Pane/Modules.hs:1184:7 before: "cr\n -- show" after: "cr\n in -- show" Please, consider reporting the bug. diff --git a/src/Ormolu/Diff.hs b/src/Ormolu/Diff.hs index b2e096e..c25b60a 100644 --- a/src/Ormolu/Diff.hs +++ b/src/Ormolu/Diff.hs @@ -14,7 +14,7 @@ import Data.Text (Text) import qualified Data.Text as T import qualified FastString as GHC import GHC -import Ormolu.Imports (sortImports) +import Ormolu.Imports (normalizeImports) import Ormolu.Parser.CommentStream import Ormolu.Parser.Result import Ormolu.Utils @@ -47,8 +47,8 @@ diffParseResult } = matchIgnoringSrcSpans cstream0 cstream1 <> matchIgnoringSrcSpans - hs0 {hsmodImports = sortImports (hsmodImports hs0)} - hs1 {hsmodImports = sortImports (hsmodImports hs1)} + hs0 {hsmodImports = normalizeImports (hsmodImports hs0)} + hs1 {hsmodImports = normalizeImports (hsmodImports hs1)} -- | Compare two values for equality disregarding differences in 'SrcSpan's -- and the ordering of import lists. diff --git a/src/Ormolu/Imports.hs b/src/Ormolu/Imports.hs index f262be2..49a8674 100644 --- a/src/Ormolu/Imports.hs +++ b/src/Ormolu/Imports.hs @@ -4,48 +4,94 @@ -- | Manipulations on import lists. module Ormolu.Imports - ( sortImports, + ( normalizeImports, ) where import Data.Bifunctor import Data.Char (isAlphaNum) import Data.Function (on) -import Data.Generics (gcompare) import Data.List (foldl', nubBy, sortBy, sortOn) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M +import FastString (FastString) import GHC hiding (GhcPs, IE) import GHC.Hs.Extension import GHC.Hs.ImpExp (IE (..)) import Ormolu.Utils (notImplemented, showOutputable) --- | Sort imports by module name. This also sorts and normalizes explicit --- import lists for each declaration. -sortImports :: [LImportDecl GhcPs] -> [LImportDecl GhcPs] -sortImports = sortBy compareIdecl . fmap (fmap sortImportLists) +-- | Sort and normalize imports. +normalizeImports :: [LImportDecl GhcPs] -> [LImportDecl GhcPs] +normalizeImports = + fmap snd + . M.toAscList + . M.fromListWith combineImports + . fmap (\x -> (importId x, g x)) where - sortImportLists :: ImportDecl GhcPs -> ImportDecl GhcPs - sortImportLists = \case - ImportDecl {..} -> + g (L l ImportDecl {..}) = + L + l ImportDecl { ideclHiding = second (fmap normalizeLies) <$> ideclHiding, .. } - XImportDecl x -> noExtCon x + g _ = notImplemented "XImportDecl" --- | Compare two @'LImportDecl' 'GhcPs'@ things. -compareIdecl :: LImportDecl GhcPs -> LImportDecl GhcPs -> Ordering -compareIdecl (L _ m0) (L _ m1) = - case (isPrelude n0, isPrelude n1) of - (False, False) -> n0 `compare` n1 - (True, False) -> GT - (False, True) -> LT - (True, True) -> m0 `gcompare` m1 +-- | 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 + { ideclHiding = case (ideclHiding, GHC.ideclHiding y) of + (Just (hiding, L l' xs), Just (_, L _ ys)) -> + Just (hiding, (L l' (normalizeLies (xs ++ ys)))) + _ -> Nothing, + .. + } +combineImports _ _ = notImplemented "XImportDecl" + +-- | 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, + importIdName :: ModuleName, + importPkgQual :: Maybe FastString, + importSource :: Bool, + importSafe :: Bool, + importQualified :: Bool, + importImplicit :: Bool, + importAs :: Maybe ModuleName, + importHiding :: Maybe Bool + } + deriving (Eq, Ord) + +-- | Obtain an 'ImportId' for a given import. +importId :: LImportDecl GhcPs -> ImportId +importId (L _ ImportDecl {..}) = + ImportId + { importIsPrelude = isPrelude, + importIdName = moduleName, + importPkgQual = sl_fs <$> ideclPkgQual, + importSource = ideclSource, + importSafe = ideclSafe, + importQualified = case ideclQualified of + QualifiedPre -> True + QualifiedPost -> True + NotQualified -> False, + importImplicit = ideclImplicit, + importAs = unLoc <$> ideclAs, + importHiding = fst <$> ideclHiding + } where - n0 = unLoc (ideclName m0) - n1 = unLoc (ideclName m1) - isPrelude = (== "Prelude") . moduleNameString + isPrelude = moduleNameString moduleName == "Prelude" + moduleName = unLoc ideclName +importId _ = notImplemented "XImportDecl" -- | Normalize a collection of import\/export items. normalizeLies :: [LIE GhcPs] -> [LIE GhcPs] diff --git a/src/Ormolu/Printer/Meat/Module.hs b/src/Ormolu/Printer/Meat/Module.hs index 259866c..8614ba6 100644 --- a/src/Ormolu/Printer/Meat/Module.hs +++ b/src/Ormolu/Printer/Meat/Module.hs @@ -11,7 +11,7 @@ where import Control.Monad import qualified Data.Text as T import GHC -import Ormolu.Imports +import Ormolu.Imports (normalizeImports) import Ormolu.Parser.CommentStream import Ormolu.Parser.Pragma import Ormolu.Parser.Shebang @@ -69,7 +69,7 @@ p_hsModule mstackHeader shebangs pragmas qualifiedPost HsModule {..} = do txt "where" newline newline - forM_ (sortImports hsmodImports) (located' (p_hsmodImport qualifiedPost)) + forM_ (normalizeImports hsmodImports) (located' (p_hsmodImport qualifiedPost)) newline switchLayout (getLoc <$> hsmodDecls) $ do p_hsDecls Free hsmodDecls diff --git a/src/Ormolu/Processing/Preprocess.hs b/src/Ormolu/Processing/Preprocess.hs index 0a2ef05..5ebf63c 100644 --- a/src/Ormolu/Processing/Preprocess.hs +++ b/src/Ormolu/Processing/Preprocess.hs @@ -11,8 +11,7 @@ where import Control.Monad import Data.Char (isSpace) import qualified Data.List as L -import Data.Maybe (isJust) -import Data.Maybe (maybeToList) +import Data.Maybe (isJust, maybeToList) import FastString import Ormolu.Config (RegionDeltas (..)) import Ormolu.Parser.Shebang (isShebang) diff --git a/src/Ormolu/Utils.hs b/src/Ormolu/Utils.hs index ff482c8..ec3c535 100644 --- a/src/Ormolu/Utils.hs +++ b/src/Ormolu/Utils.hs @@ -22,8 +22,8 @@ where import Data.Char (isSpace) import Data.List (dropWhileEnd) -import qualified Data.List.NonEmpty as NE import Data.List.NonEmpty (NonEmpty (..)) +import qualified Data.List.NonEmpty as NE import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T