on master help sign in
Format records with a single data constructor more compactly
commitd09429f6ec56b66dcb90b967f32bfa6560601d38 authorMark Karpov <markkarpov92@gmail.com> dateFri 10 Apr 2020, 17:52 (7 years ago) parentd96f7e3
12 files changed+168−124
CHANGELOG.md+3 app/Main.hs+8−9 data/examples/declaration/data/field-layout/record-out.hs+9−10 data/examples/declaration/data/record-multi-const-out.hs+17 data/examples/declaration/data/record-multi-const.hs+13 data/examples/declaration/data/record-out.hs+19−20 data/examples/declaration/data/simple-broken-out.hs+1−2 data/examples/declaration/instance/data-family-instances-newtype-out.hs+3−4 src/Ormolu/Config.hs+17−19 src/Ormolu/Parser/Result.hs+16−17 src/Ormolu/Printer/Internal.hs+35−37 src/Ormolu/Printer/Meat/Declaration/Data.hs+27−6
CHANGELOG.md+3
@@ -24,6 +24,9 @@
2424 cause the data constructor be unconditionally rendered in multiline layout
2525 [Issue 427](https://github.com/tweag/ormolu/issues/427).
2626
27+* Records with a single data constructor are now formatted more compactly.
28+ [Issue 425](https://github.com/tweag/ormolu/issues/425).
29+
2730* Implemented support for the new language extension `ImportQualifiedPost`.
2831
2932* Implemented support for the new language extension
app/Main.hs+8−9
@@ -81,15 +81,14 @@ formatOne mode config = \case
8181----------------------------------------------------------------------------
8282-- Command line options parsing.
8383
84−data Opts
85− = Opts
86− { -- | Mode of operation
87− optMode :: !Mode,
88− -- | Ormolu 'Config'
89− optConfig :: !Config,
90− -- | Haskell source files to format or stdin (when the list is empty)
91− optInputFiles :: ![FilePath]
92− }
84+data Opts = Opts
85+ { -- | Mode of operation
86+ optMode :: !Mode,
87+ -- | Ormolu 'Config'
88+ optConfig :: !Config,
89+ -- | Haskell source files to format or stdin (when the list is empty)
90+ optInputFiles :: ![FilePath]
91+ }
9392
9493-- | Mode of operation.
9594data Mode
data/examples/declaration/data/field-layout/record-out.hs+9−10
@@ -1,13 +1,12 @@
11module Main where
22
33-- | Foo.
4−data Foo
5− = Foo
6− { -- | Something
7− foo :: Foo Int Int,
8− -- | Something else
9− bar ::
10− Bar
11− Char
12− Char
13− }
4+data Foo = Foo
5+ { -- | Something
6+ foo :: Foo Int Int,
7+ -- | Something else
8+ bar ::
9+ Bar
10+ Char
11+ Char
12+ }
data/examples/declaration/data/record-multi-const-out.hsnew file+17
@@ -0,0 +1,17 @@
1+module Main where
2+
3+-- | Something.
4+data Foo
5+ = Foo
6+ { -- | X
7+ fooX :: Int,
8+ -- | Y
9+ fooY :: Int
10+ }
11+ | Bar
12+ { -- | X
13+ barX :: Int,
14+ -- | Y
15+ barY :: Int
16+ }
17+ deriving (Eq, Show)
data/examples/declaration/data/record-multi-const.hsnew file+13
@@ -0,0 +1,13 @@
1+module Main where
2+
3+-- | Something.
4+
5+data Foo = Foo
6+ { fooX :: Int -- ^ X
7+ , fooY :: Int -- ^ Y
8+ } |
9+ Bar
10+ { barX :: Int -- ^ X
11+ , barY :: Int -- ^ Y
12+ }
13+ deriving (Eq, Show)
data/examples/declaration/data/record-out.hs+19−20
@@ -1,24 +1,23 @@
11module Main where
22
33-- | Something.
4−data Foo
5− = Foo
6− { -- | X
7− fooX :: Int,
8− -- | Y
9− fooY :: Int,
10− -- | BarBaz
11− fooBar, fooBaz :: NonEmpty (Identity Bool),
12− -- | GagGog
13− fooGag,
14− fooGog ::
15− NonEmpty
16− ( Indentity
17− Bool
18− ),
19− -- | Huh!
20− fooFoo,
21− barBar ::
22− Int
23− }
4+data Foo = Foo
5+ { -- | X
6+ fooX :: Int,
7+ -- | Y
8+ fooY :: Int,
9+ -- | BarBaz
10+ fooBar, fooBaz :: NonEmpty (Identity Bool),
11+ -- | GagGog
12+ fooGag,
13+ fooGog ::
14+ NonEmpty
15+ ( Indentity
16+ Bool
17+ ),
18+ -- | Huh!
19+ fooFoo,
20+ barBar ::
21+ Int
22+ }
2423 deriving (Eq, Show)
data/examples/declaration/data/simple-broken-out.hs+1−2
@@ -1,8 +1,7 @@
11module Main where
22
33-- | Here we go.
4−data Foo
5− = Foo {unFoo :: Int}
4+data Foo = Foo {unFoo :: Int}
65 deriving (Eq)
76
87-- | And once again.
data/examples/declaration/instance/data-family-instances-newtype-out.hs+3−4
@@ -1,7 +1,6 @@
11{-# LANGUAGE GADTSyntax #-}
22{-# LANGUAGE TypeFamilies #-}
33
4−newtype instance Foo [Double]
5− = DoubleListFoo
6− { unDoubleListFoo :: Double
7− }
4+newtype instance Foo [Double] = DoubleListFoo
5+ { unDoubleListFoo :: Double
6+ }
src/Ormolu/Config.hs+17−19
@@ -10,21 +10,20 @@ where
1010import qualified SrcLoc as GHC
1111
1212-- | Ormolu configuration.
13−data Config
14− = Config
15− { -- | Dynamic options to pass to GHC parser
16− cfgDynOptions :: ![DynOption],
17− -- | Do formatting faster but without automatic detection of defects
18− cfgUnsafe :: !Bool,
19− -- | Output information useful for debugging
20− cfgDebug :: !Bool,
21− -- | Do not fail if CPP pragma is present (still doesn't handle CPP but
22− -- useful for formatting of files that enable the extension without
23− -- actually containing CPP macros)
24− cfgTolerateCpp :: !Bool,
25− -- | Checks if re-formatting the result is idempotent.
26− cfgCheckIdempotency :: !Bool
27− }
13+data Config = Config
14+ { -- | Dynamic options to pass to GHC parser
15+ cfgDynOptions :: ![DynOption],
16+ -- | Do formatting faster but without automatic detection of defects
17+ cfgUnsafe :: !Bool,
18+ -- | Output information useful for debugging
19+ cfgDebug :: !Bool,
20+ -- | Do not fail if CPP pragma is present (still doesn't handle CPP but
21+ -- useful for formatting of files that enable the extension without
22+ -- actually containing CPP macros)
23+ cfgTolerateCpp :: !Bool,
24+ -- | Checks if re-formatting the result is idempotent.
25+ cfgCheckIdempotency :: !Bool
26+ }
2827 deriving (Eq, Show)
2928
3029-- | Default 'Config'.
@@ -39,10 +38,9 @@ defaultConfig =
3938 }
4039
4140-- | A wrapper for dynamic options.
42−newtype DynOption
43− = DynOption
44− { unDynOption :: String
45− }
41+newtype DynOption = DynOption
42+ { unDynOption :: String
43+ }
4644 deriving (Eq, Ord, Show)
4745
4846-- | Convert 'DynOption' to @'GHC.Located' 'String'@.
src/Ormolu/Parser/Result.hs+16−17
@@ -13,23 +13,22 @@ import Ormolu.Parser.CommentStream
1313import Ormolu.Parser.Pragma (Pragma)
1414
1515-- | A collection of data that represents a parsed module in Ormolu.
16−data ParseResult
17− = ParseResult
18− { -- | 'ParsedSource' from GHC
19− prParsedSource :: ParsedSource,
20− -- | Ormolu-specfic representation of annotations
21− prAnns :: Anns,
22− -- | Comment stream
23− prCommentStream :: CommentStream,
24− -- | Extensions enabled in that module
25− prExtensions :: [Pragma],
26− -- | Shebangs found in the input
27− prShebangs :: [Located String],
28− -- | Whether or not record dot syntax is enabled
29− prUseRecordDot :: Bool,
30− -- | Whether or not ImportQualifiedPost is enabled
31− prImportQualifiedPost :: Bool
32− }
16+data ParseResult = ParseResult
17+ { -- | 'ParsedSource' from GHC
18+ prParsedSource :: ParsedSource,
19+ -- | Ormolu-specfic representation of annotations
20+ prAnns :: Anns,
21+ -- | Comment stream
22+ prCommentStream :: CommentStream,
23+ -- | Extensions enabled in that module
24+ prExtensions :: [Pragma],
25+ -- | Shebangs found in the input
26+ prShebangs :: [Located String],
27+ -- | Whether or not record dot syntax is enabled
28+ prUseRecordDot :: Bool,
29+ -- | Whether or not ImportQualifiedPost is enabled
30+ prImportQualifiedPost :: Bool
31+ }
3332
3433-- | Pretty-print a 'ParseResult'.
3534prettyPrintParseResult :: ParseResult -> String
src/Ormolu/Printer/Internal.hs+35−37
@@ -73,45 +73,43 @@ newtype R a = R (ReaderT RC (State SC) a)
7373
7474-- | Reader context of 'R'. This should be used when we control rendering by
7575-- enclosing certain expressions with wrappers.
76−data RC
77− = RC
78− { -- | Indentation level, as the column index we need to start from after
79− -- a newline if we break lines
80− rcIndent :: !Int,
81− -- | Current layout
82− rcLayout :: Layout,
83− -- | Spans of enclosing elements of AST
84− rcEnclosingSpans :: [RealSrcSpan],
85− -- | Collection of annotations
86− rcAnns :: Anns,
87− -- | Whether the last expression in the layout can use braces
88− rcCanUseBraces :: Bool,
89− -- | Whether the source could have used the record dot preprocessor
90− rcUseRecDot :: Bool
91− }
76+data RC = RC
77+ { -- | Indentation level, as the column index we need to start from after
78+ -- a newline if we break lines
79+ rcIndent :: !Int,
80+ -- | Current layout
81+ rcLayout :: Layout,
82+ -- | Spans of enclosing elements of AST
83+ rcEnclosingSpans :: [RealSrcSpan],
84+ -- | Collection of annotations
85+ rcAnns :: Anns,
86+ -- | Whether the last expression in the layout can use braces
87+ rcCanUseBraces :: Bool,
88+ -- | Whether the source could have used the record dot preprocessor
89+ rcUseRecDot :: Bool
90+ }
9291
9392-- | State context of 'R'.
94−data SC
95− = SC
96− { -- | Index of the next column to render
97− scColumn :: !Int,
98− -- | Rendered source code so far
99− scBuilder :: Builder,
100− -- | Span stream
101− scSpanStream :: SpanStream,
102− -- | Comment stream
103− scCommentStream :: CommentStream,
104− -- | Pending comment lines (in reverse order) to be inserted before next
105− -- newline, 'Int' is the indentation level
106− scPendingComments :: ![(CommentPosition, Int, Text)],
107− -- | Whether the current line is “dirty”, that is, already contains
108− -- atoms that can have comments attached to them
109− scDirtyLine :: !Bool,
110− -- | Whether to output a space before the next output
111− scRequestedDelimiter :: !RequestedDelimiter,
112− -- | Span of last output comment
113− scLastCommentSpan :: !(Maybe (Maybe HaddockStyle, RealSrcSpan))
114− }
93+data SC = SC
94+ { -- | Index of the next column to render
95+ scColumn :: !Int,
96+ -- | Rendered source code so far
97+ scBuilder :: Builder,
98+ -- | Span stream
99+ scSpanStream :: SpanStream,
100+ -- | Comment stream
101+ scCommentStream :: CommentStream,
102+ -- | Pending comment lines (in reverse order) to be inserted before next
103+ -- newline, 'Int' is the indentation level
104+ scPendingComments :: ![(CommentPosition, Int, Text)],
105+ -- | Whether the current line is “dirty”, that is, already contains
106+ -- atoms that can have comments attached to them
107+ scDirtyLine :: !Bool,
108+ -- | Whether to output a space before the next output
109+ scRequestedDelimiter :: !RequestedDelimiter,
110+ -- | Span of last output comment
111+ scLastCommentSpan :: !(Maybe (Maybe HaddockStyle, RealSrcSpan))
112+ }
115113
116114-- | Make sure next output is delimited by one of the following.
117115data RequestedDelimiter
src/Ormolu/Printer/Meat/Declaration/Data.hs+27−6
@@ -59,25 +59,35 @@ p_dataDecl style name tpats fixity HsDataDefn {..} = do
5959 breakpoint
6060 txt "where"
6161 breakpoint
62− sepSemi (located' p_conDecl) dd_cons
62+ sepSemi (located' (p_conDecl False)) dd_cons
6363 else switchLayout (getLoc name : (getLoc <$> dd_cons))
6464 $ inci
6565 $ do
66− breakpoint
66+ let singleConstRec = isSingleConstRec dd_cons
67+ if singleConstRec
68+ then space
69+ else breakpoint
6770 txt "="
6871 space
6972 let s =
7073 vlayout
7174 (space >> txt "|" >> space)
7275 (newline >> txt "|" >> space)
73− sep s (sitcc . located' p_conDecl) dd_cons
76+ sitcc' =
77+ if singleConstRec
78+ then id
79+ else sitcc
80+ sep s (sitcc' . located' (p_conDecl singleConstRec)) dd_cons
7481 unless (null $ unLoc dd_derivs) breakpoint
7582 inci . located dd_derivs $ \xs ->
7683 sep newline (located' p_hsDerivingClause) xs
7784p_dataDecl _ _ _ _ (XHsDataDefn x) = noExtCon x
7885
79−p_conDecl :: ConDecl GhcPs -> R ()
80−p_conDecl = \case
86+p_conDecl ::
87+ Bool ->
88+ ConDecl GhcPs ->
89+ R ()
90+p_conDecl singleConstRec = \case
8191 ConDeclGADT {..} -> do
8292 mapM_ (p_hsDocString Pipe True) con_doc
8393 let conDeclSpn =
@@ -144,7 +154,11 @@ p_conDecl = \case
144154 RecCon l -> do
145155 p_rdrName con_name
146156 breakpoint
147− inci $ located l p_conDeclFields
157+ let inci' =
158+ if singleConstRec
159+ then id
160+ else inci
161+ inci' (located l p_conDeclFields)
148162 InfixCon x y -> do
149163 located x p_hsType
150164 breakpoint
@@ -235,3 +249,10 @@ isInfix :: LexicalFixity -> Bool
235249isInfix = \case
236250 Infix -> True
237251 Prefix -> False
252+
253+isSingleConstRec :: [LConDecl GhcPs] -> Bool
254+isSingleConstRec [(L _ ConDeclH98 {..})] =
255+ case con_args of
256+ RecCon _ -> True
257+ _ -> False
258+isSingleConstRec _ = False