on master help sign in
Take non-commutative nature of language pragmas into account

The order in which language pragmas are put in the input sometimes matters. This is because some language extensions can enable other extensions, yet the extensions coming later in the list have the ability to change it. So here we classify all extensions by assigning one of the four groups to them. Then we only sort inside of the groups.

commit5ca92059fb59e891ac36b3abf735d242b28f50dc authormrkkrp <mark.karpov@tweag.io> dateThu 28 Nov 2019, 16:30 (7 years ago) parent6e07126
5 files changed+73−4
CHANGELOG.md+3 data/examples/declaration/class/type-operators3-out.hs+1−1 data/examples/other/pragma-sorting-out.hs+12 data/examples/other/pragma-sorting.hs+10 src/Ormolu/Printer/Meat/Pragma.hs+47−3
CHANGELOG.md+3
@@ -38,6 +38,9 @@
3838* Implemented correct handling of stack headers. [Issue
3939 393](https://github.com/tweag/ormolu/issues/393).
4040
41+* Sorting language pragmas cannot not change meaning of the input program
42+ anymore. [Issue 404](https://github.com/tweag/ormolu/issues/404).
43+
4144## Ormolu 0.0.1.0
4245
4346* Initial release.
data/examples/declaration/class/type-operators3-out.hs+1−1
@@ -1,7 +1,7 @@
1−{-# LANGUAGE NoStarIsType #-}
21{-# LANGUAGE PolyKinds #-}
32{-# LANGUAGE TypeFamilies #-}
43{-# LANGUAGE TypeOperators #-}
4+{-# LANGUAGE NoStarIsType #-}
55
66class PNum x where
77 type (a :: x) * (b :: x)
data/examples/other/pragma-sorting-out.hsnew file+12
@@ -0,0 +1,12 @@
1+{-# LANGUAGE NondecreasingIndentation #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+{-# LANGUAGE TypeFamilies #-}
4+{-# LANGUAGE NoMonoLocalBinds #-}
5+
6+-- This gap is necessary for stylish Haskell not to re-arrange
7+-- NoMonoLocalBinds before TypeFamilies
8+
9+module Foo
10+ ( bar,
11+ )
12+where
data/examples/other/pragma-sorting.hsnew file+10
@@ -0,0 +1,10 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+{-# LANGUAGE TypeFamilies #-}
3+{-# LANGUAGE NondecreasingIndentation #-}
4+-- This gap is necessary for stylish Haskell not to re-arrange
5+-- NoMonoLocalBinds before TypeFamilies
6+{-# LANGUAGE NoMonoLocalBinds #-}
7+
8+module Foo (
9+ bar
10+ ) where
src/Ormolu/Printer/Meat/Pragma.hs+47−3
@@ -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