@@ -8,18 +8,46 @@ module Ormolu.Printer.Meat.Pragma
88 )
99where
1010
11+import Data.Char (isUpper)
12+import Data.Maybe (listToMaybe)
1113import qualified Data.Set as S
1214import qualified Data.Text as T
1315import Ormolu.Parser.Pragma (Pragma (..))
1416import Ormolu.Printer.Combinators
1517
16−data PragmaTy = Language | OptionsGHC | OptionsHaddock
18+-- | Pragma classification.
19+data PragmaTy
20+ = Language LanguagePragmaClass
21+ | OptionsGHC
22+ | OptionsHaddock
23+ deriving (Eq, Ord)
24+
25+-- | Language pragma classification.
26+--
27+-- The order in which language pragmas are put in the input sometimes
28+-- matters. This is because some language extensions can enable other
29+-- extensions, yet the extensions coming later in the list have the ability
30+-- to change it. So here we classify all extensions by assigning one of the
31+-- four groups to them. Then we only sort inside of the groups.
32+--
33+-- 'Ord' instance of this data type is what affects the sorting.
34+--
35+-- See also: <https://github.com/tweag/ormolu/issues/404>
36+data LanguagePragmaClass
37+ = -- | All other extensions
38+ Normal
39+ | -- | Extensions starting with "No"
40+ Disabling
41+ | -- | Extensions that should go after everything else
42+ Final
1743 deriving (Eq, Ord)
1844
1945p_pragmas :: [Pragma] -> R ()
2046p_pragmas ps =
2147 let prepare = concatMap $ \case
22− PragmaLanguage xs -> (Language,) <$> xs
48+ PragmaLanguage xs ->
49+ let f x = (Language (classifyLanguagePragma x), x)
50+ in f <$> xs
2351 PragmaOptionsGHC x -> [(OptionsGHC, x)]
2452 PragmaOptionsHaddock x -> [(OptionsHaddock, x)]
2553 in mapM_ (uncurry p_pragma) (S.toAscList . S.fromList . prepare $ ps)
@@ -28,10 +56,26 @@ p_pragma :: PragmaTy -> String -> R ()
2856p_pragma ty c = do
2957 txt "{-# "
3058 txt $ case ty of
31− Language -> "LANGUAGE"
59+ Language _ -> "LANGUAGE"
3260 OptionsGHC -> "OPTIONS_GHC"
3361 OptionsHaddock -> "OPTIONS_HADDOCK"
3462 space
3563 txt (T.pack c)
3664 txt " #-}"
3765 newline
66+
67+-- | Classify a 'LanguagePragma'.
68+classifyLanguagePragma :: String -> LanguagePragmaClass
69+classifyLanguagePragma = \case
70+ "ImplicitPrelude" -> Final
71+ "CUSKs" -> Final
72+ str ->
73+ case splitAt 2 str of
74+ ("No", rest) ->
75+ case listToMaybe rest of
76+ Nothing -> Normal
77+ Just x ->
78+ if isUpper x
79+ then Disabling
80+ else Normal
81+ _ -> Normal