on master help sign in
Preserve grouping of do statements
commit145f7296f54e39a3a55ee5206602d73a9102cc2b authorMark Karpov <markkarpov92@gmail.com> dateThu 16 Apr 2020, 13:40 (7 years ago) parent44cef92
18 files changed+160−65
CHANGELOG.md+5 data/examples/declaration/value/function/do/blocks-out.hs+1 data/examples/declaration/value/function/do/comment-alignment-out.hs+9 data/examples/declaration/value/function/do/comment-alignment.hs+9 data/examples/declaration/value/function/do/comment-spacing-out.hs+7 data/examples/declaration/value/function/do/comment-spacing.hs+7 data/examples/declaration/value/function/do/recursive-do-rec-out.hs+5 expected-failures/Agda.txt+2−2 expected-failures/brick.txt+1−1 expected-failures/idris.txt+3−3 expected-failures/intero.txt+1−1 expected-failures/leksah.txt+4−4 expected-failures/pandoc.txt+1−1 src/Ormolu/Printer/Combinators.hs+5−3 src/Ormolu/Printer/Comments.hs+31−27 src/Ormolu/Printer/Internal.hs+38−16 src/Ormolu/Printer/Meat/Common.hs+6−4 src/Ormolu/Printer/Meat/Declaration/Value.hs+25−3
CHANGELOG.md+5
@@ -1,3 +1,8 @@
1+## Unreleased
2+
3+* Grouping of statements in `do`-blocks is now respected. [Issue
4+ 74](https://github.com/tweag/ormolu/issues/74).
5+
16## Ormolu 0.0.4.0
27
38* When given several files to format, Ormolu does not stop on the first
data/examples/declaration/value/function/do/blocks-out.hs+1
@@ -15,4 +15,5 @@ readInClause = do
1515 <|> do
1616 optional g_Semi
1717 void allspacing
18+
1819 return things
data/examples/declaration/value/function/do/comment-alignment-out.hsnew file+9
@@ -0,0 +1,9 @@
1+main = do
2+ case blah of
3+ Nothing -> return ()
4+ Just xs -> do
5+ forM_ xs foo
6+ bar
7+ -- and here it goes
8+ unless bobla $
9+ quux
data/examples/declaration/value/function/do/comment-alignment.hsnew file+9
@@ -0,0 +1,9 @@
1+main = do
2+ case blah of
3+ Nothing -> return ()
4+ Just xs -> do
5+ forM_ xs foo
6+ bar
7+ -- and here it goes
8+ unless bobla $
9+ quux
data/examples/declaration/value/function/do/comment-spacing-out.hsnew file+7
@@ -0,0 +1,7 @@
1+foo = do
2+ bar
3+ -- foo
4+ baz
5+
6+ -- foo
7+ quux
data/examples/declaration/value/function/do/comment-spacing.hsnew file+7
@@ -0,0 +1,7 @@
1+foo = do
2+ bar
3+ -- foo
4+ baz
5+
6+ -- foo
7+ quux
data/examples/declaration/value/function/do/recursive-do-rec-out.hs+5
@@ -2,13 +2,18 @@
22
33foo = do
44 rec a <- b + 5
5+
56 let d = c
7+
68 b <- a * 5
9+
710 something
11+
812 c <- a + b
913 print c
1014 rec something $ do
1115 x <- a
1216 print x
17+
1318 y <- c
1419 print y
expected-failures/Agda.txt+2−2
@@ -11,7 +11,7 @@ Formatting is not idempotent:
1111Please, consider reporting the bug.
1212
1313Parsing of formatted code failed:
14− src/full/Agda/Syntax/Translation/ConcreteToAbstract.hs<rendered>:509:7-13
14+ src/full/Agda/Syntax/Translation/ConcreteToAbstract.hs<rendered>:525:7-13
1515 parse error on input `C.QName'
1616Please, consider reporting the bug.
1717
@@ -22,7 +22,7 @@ Formatting is not idempotent:
2222Please, consider reporting the bug.
2323
2424Formatting is not idempotent:
25− src/full/Agda/TypeChecking/MetaVars.hs<rendered>:1419:16
25+ src/full/Agda/TypeChecking/MetaVars.hs<rendered>:1448:16
2626 before: "ubstCand = -- | a po"
2727 after: "ubstCand =\n -- | a "
2828Please, consider reporting the bug.
expected-failures/brick.txt+1−1
@@ -1,5 +1,5 @@
11Formatting is not idempotent:
2− src/Brick/Widgets/List.hs<rendered>:571:3
2+ src/Brick/Widgets/List.hs<rendered>:573:3
33 before: "t at i\n in -- If t"
44 after: "t at i\n -"
55Please, consider reporting the bug.
expected-failures/idris.txt+3−3
@@ -35,7 +35,7 @@ Formatting is not idempotent:
3535Please, consider reporting the bug.
3636
3737Formatting is not idempotent:
38− src/Idris/Elab/Term.hs<rendered>:423:47
38+ src/Idris/Elab/Term.hs<rendered>:432:47
3939 before: "K\" _ _)) = -- for ch"
4040 after: "K\" _ _)) =\n -- "
4141Please, consider reporting the bug.
@@ -65,13 +65,13 @@ The GHC parser (in Haddock mode) failed:
6565 parse error on input `@'
6666
6767Formatting is not idempotent:
68− src/Idris/PartialEval.hs<rendered>:280:42
68+ src/Idris/PartialEval.hs<rendered>:281:42
6969 before: ": ns) as = -- Droppe"
7070 after: ": ns) as =\n -- "
7171Please, consider reporting the bug.
7272
7373Formatting is not idempotent:
74− src/Idris/ProofSearch.hs<rendered>:483:20
74+ src/Idris/ProofSearch.hs<rendered>:487:20
7575 before: "OK ty hs = -- if any"
7676 after: "OK ty hs =\n -- "
7777Please, consider reporting the bug.
expected-failures/intero.txt+1−1
@@ -1,5 +1,5 @@
11Formatting is not idempotent:
2− src/InteractiveUI.hs<rendered>:2595:36
2+ src/InteractiveUI.hs<rendered>:2635:36
33 before: "Decl d2) = -- A bit "
44 after: "Decl d2) =\n -- A bi"
55Please, consider reporting the bug.
expected-failures/leksah.txt+4−4
@@ -1,11 +1,11 @@
11Formatting is not idempotent:
2− src/IDE/Pane/Modules.hs<rendered>:1182:7
2+ src/IDE/Pane/Modules.hs<rendered>:1188:7
33 before: "cr\n -- show"
44 after: "cr\n in -- show"
55Please, consider reporting the bug.
66
77Formatting is not idempotent:
8− src/IDE/SymbolNavigation.hs<rendered>:229:8
9− before: ")\n return [\n {-i"
10− after: ")\n return\n [ {-i"
8+ src/IDE/SymbolNavigation.hs<rendered>:233:8
9+ before: "\n\n return [\n {-i"
10+ after: "\n\n return\n [ {-i"
1111Please, consider reporting the bug.
expected-failures/pandoc.txt+1−1
@@ -9,7 +9,7 @@ Formatting is not idempotent:
99Please, consider reporting the bug.
1010
1111Formatting is not idempotent:
12− src/Text/Pandoc/Writers/Docx.hs<rendered>:960:25
12+ src/Text/Pandoc/Writers/Docx.hs<rendered>:997:25
1313 before: " -- w:p\n "
1414 after: " -- w:p\n "
1515Please, consider reporting the bug.
src/Ormolu/Printer/Combinators.hs+5−3
@@ -52,10 +52,12 @@ module Ormolu.Printer.Combinators
5252 -- ** Literals
5353 comma,
5454
55− -- ** Comments
55+ -- ** Stateful markers
56+ SpanMark (..),
57+ spanMarkSpan,
5658 HaddockStyle (..),
57− setLastCommentSpan,
58− getLastCommentSpan,
59+ setSpanMark,
60+ getSpanMark,
5961 )
6062where
6163
src/Ormolu/Printer/Comments.hs+31−27
@@ -1,3 +1,4 @@
1+{-# LANGUAGE LambdaCase #-}
12{-# LANGUAGE OverloadedStrings #-}
23
34-- | Helpers for formatting of comments. This is low-level code, use
@@ -35,10 +36,10 @@ spitPrecedingComments ::
3536spitPrecedingComments ref = do
3637 gotSome <- handleCommentSeries (spitPrecedingComment ref)
3738 when gotSome $ do
39+ lastMark <- getSpanMark
3840 -- Insert a blank line between the preceding comments and the thing
3941 -- after them if there was a blank line in the input.
40− lastSpn <- fmap snd <$> getLastCommentSpan
41− when (needsNewlineBefore (getRealSrcSpan ref) lastSpn) newline
42+ when (needsNewlineBefore (getRealSrcSpan ref) lastMark) newline
4243
4344-- | Output all comments following an element at given location.
4445spitFollowingComments ::
@@ -73,14 +74,14 @@ spitPrecedingComment ::
7374 -- | AST element to attach comments to
7475 RealLocated a ->
7576 -- | Location of last comment in the series
76− Maybe RealSrcSpan ->
77+ Maybe SpanMark ->
7778 -- | Are we done?
7879 R Bool
79−spitPrecedingComment (L ref a) mlastSpn = do
80+spitPrecedingComment (L ref a) mlastMark = do
8081 let p (L l _) = realSrcSpanEnd l <= realSrcSpanStart ref
8182 withPoppedComment p $ \l comment -> do
8283 dirtyLine <-
83− case mlastSpn of
84+ case mlastMark of
8485 -- When the current line is dirty it means that something that can
8586 -- have comments attached to it is already on the line. To avoid
8687 -- problems with idempotence we cannot output the first comment
@@ -89,7 +90,7 @@ spitPrecedingComment (L ref a) mlastSpn = do
8990 -- an extra 'newline' in this case.
9091 Nothing -> isLineDirty -- only for very first preceding comment
9192 Just _ -> return False
92− when (dirtyLine || needsNewlineBefore l mlastSpn) newline
93+ when (dirtyLine || needsNewlineBefore l mlastMark) newline
9394 spitCommentNow l comment
9495 if theSameLinePre l ref && not (isModule a)
9596 then space
@@ -102,34 +103,34 @@ spitFollowingComment ::
102103 -- | AST element to attach comments to
103104 RealLocated a ->
104105 -- | Location of last comment in the series
105− Maybe RealSrcSpan ->
106+ Maybe SpanMark ->
106107 -- | Are we done?
107108 R Bool
108−spitFollowingComment (L ref a) mlastSpn = do
109+spitFollowingComment (L ref a) mlastMark = do
109110 mnSpn <- nextEltSpan
110111 -- Get first enclosing span that is not equal to reference span, i.e. it's
111112 -- truly something enclosing the AST element.
112113 meSpn <- getEnclosingSpan (/= ref)
113− withPoppedComment (commentFollowsElt ref mnSpn meSpn mlastSpn) $ \l comment ->
114+ withPoppedComment (commentFollowsElt ref mnSpn meSpn mlastMark) $ \l comment ->
114115 if theSameLinePost l ref && not (isModule a)
115116 then
116117 if isMultilineComment comment
117118 then space >> spitCommentNow l comment
118119 else spitCommentPending OnTheSameLine l comment
119120 else do
120− when (needsNewlineBefore l mlastSpn) $
121+ when (needsNewlineBefore l mlastMark) $
121122 registerPendingCommentLine OnNextLine ""
122123 spitCommentPending OnNextLine l comment
123124
124125-- | Output a single remaining comment from the comment stream.
125126spitRemainingComment ::
126127 -- | Location of last comment in the series
127− Maybe RealSrcSpan ->
128+ Maybe SpanMark ->
128129 -- | Are we done?
129130 R Bool
130−spitRemainingComment mlastSpn =
131+spitRemainingComment mlastMark =
131132 withPoppedComment (const True) $ \l comment -> do
132− when (needsNewlineBefore l mlastSpn) newline
133+ when (needsNewlineBefore l mlastMark) newline
133134 spitCommentNow l comment
134135 newline
135136
@@ -140,13 +141,13 @@ spitRemainingComment mlastSpn =
140141handleCommentSeries ::
141142 -- | Given location of previous comment, output the next comment
142143 -- returning 'True' if we're done
143− (Maybe RealSrcSpan -> R Bool) ->
144+ (Maybe SpanMark -> R Bool) ->
144145 -- | Whether we printed any comments
145146 R Bool
146147handleCommentSeries f = go False
147148 where
148149 go gotSome = do
149− done <- getLastCommentSpan >>= f . fmap snd
150+ done <- getSpanMark >>= f
150151 if done
151152 then return gotSome
152153 else go True
@@ -172,13 +173,13 @@ needsNewlineBefore ::
172173 -- | Current comment span
173174 RealSrcSpan ->
174175 -- | Last printed comment span
175− Maybe RealSrcSpan ->
176+ Maybe SpanMark ->
176177 Bool
177−needsNewlineBefore l mlastSpn =
178− case mlastSpn of
178+needsNewlineBefore l mlastMark =
179+ case spanMarkSpan <$> mlastMark of
179180 Nothing -> False
180− Just lastSpn ->
181− srcSpanStartLine l > srcSpanEndLine lastSpn + 1
181+ Just lastMark ->
182+ srcSpanStartLine l > srcSpanEndLine lastMark + 1
182183
183184-- | Is the preceding comment and AST element are on the same line?
184185theSameLinePre ::
@@ -209,11 +210,11 @@ commentFollowsElt ::
209210 -- | Location of enclosing AST element
210211 Maybe RealSrcSpan ->
211212 -- | Location of last comment in the series
212− Maybe RealSrcSpan ->
213+ Maybe SpanMark ->
213214 -- | Comment to test
214215 RealLocated Comment ->
215216 Bool
216−commentFollowsElt ref mnSpn meSpn mlastSpn (L l comment) =
217+commentFollowsElt ref mnSpn meSpn mlastMark (L l comment) =
217218 -- A comment follows a AST element if all 4 conditions are satisfied:
218219 goesAfter
219220 && logicallyFollows
@@ -255,9 +256,12 @@ commentFollowsElt ref mnSpn meSpn mlastSpn (L l comment) =
255256 >= abs (startColumn ref - startColumn l)
256257 )
257258 continuation =
258− case mlastSpn of
259− Nothing -> False
260− Just spn -> srcSpanEndLine spn + 1 == srcSpanStartLine l
259+ case mlastMark of
260+ Just (HaddockSpan _ spn) ->
261+ srcSpanEndLine spn + 1 == srcSpanStartLine l
262+ Just (CommentSpan spn) ->
263+ srcSpanEndLine spn + 1 == srcSpanStartLine l
264+ _ -> False
261265 lastInEnclosing =
262266 case meSpn of
263267 -- When there is no enclosing element, return false
@@ -281,7 +285,7 @@ spitCommentNow spn comment = do
281285 . fmap (txt . T.pack)
282286 . coerce
283287 $ comment
284− setLastCommentSpan Nothing spn
288+ setSpanMark (CommentSpan spn)
285289
286290-- | Output a 'Comment' at the end of correct line or after it depending on
287291-- 'CommentPosition'. Used for comments that may potentially follow on the
@@ -297,4 +301,4 @@ spitCommentPending position spn comment = do
297301 . fmap (registerPendingCommentLine position . T.pack)
298302 . coerce
299303 $ comment
300− setLastCommentSpan Nothing spn
304+ setSpanMark (CommentSpan spn)
src/Ormolu/Printer/Internal.hs+38−16
@@ -1,4 +1,5 @@
11{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2+{-# LANGUAGE LambdaCase #-}
23{-# LANGUAGE OverloadedStrings #-}
34{-# LANGUAGE RankNTypes #-}
45
@@ -38,9 +39,13 @@ module Ormolu.Printer.Internal
3839 popComment,
3940 getEnclosingSpan,
4041 withEnclosingSpan,
42+
43+ -- * Stateful markers
44+ SpanMark (..),
45+ spanMarkSpan,
4146 HaddockStyle (..),
42− setLastCommentSpan,
43− getLastCommentSpan,
47+ setSpanMark,
48+ getSpanMark,
4449
4550 -- * Annotations
4651 getAnns,
@@ -107,8 +112,8 @@ data SC = SC
107112 scDirtyLine :: !Bool,
108113 -- | Whether to output a space before the next output
109114 scRequestedDelimiter :: !RequestedDelimiter,
110− -- | Span of last output comment
111− scLastCommentSpan :: !(Maybe (Maybe HaddockStyle, RealSrcSpan))
115+ -- | An auxiliary marker for keeping track of last output element
116+ scSpanMark :: !(Maybe SpanMark)
112117 }
113118
114119-- | Make sure next output is delimited by one of the following.
@@ -176,7 +181,7 @@ runR (R m) sstream cstream anns recDot =
176181 scPendingComments = [],
177182 scDirtyLine = False,
178183 scRequestedDelimiter = VeryBeginning,
179− scLastCommentSpan = Nothing
184+ scSpanMark = Nothing
180185 }
181186
182187----------------------------------------------------------------------------
@@ -239,11 +244,11 @@ spit dirty printingComments txt' = do
239244 scColumn = scColumn sc + T.length indentedTxt,
240245 scDirtyLine = scDirtyLine sc || dirty,
241246 scRequestedDelimiter = RequestedNothing,
242− scLastCommentSpan =
247+ scSpanMark =
243248 -- If there are pending comments, do not reset last comment
244249 -- location.
245250 if printingComments || (not . null . scPendingComments) sc
246− then scLastCommentSpan sc
251+ then scSpanMark sc
247252 else Nothing
248253 }
249254
@@ -463,6 +468,25 @@ withEnclosingSpan spn (R m) = R (local modRC m)
463468 { rcEnclosingSpans = spn : rcEnclosingSpans rc
464469 }
465470
471+----------------------------------------------------------------------------
472+-- Stateful markers
473+
474+-- | An auxiliary marker for keeping track of last output element.
475+data SpanMark
476+ = -- | Haddock comment
477+ HaddockSpan HaddockStyle RealSrcSpan
478+ | -- | Non-haddock comment
479+ CommentSpan RealSrcSpan
480+ | -- | Non-comment span
481+ OtherSpan RealSrcSpan
482+
483+-- | Project 'RealSrcSpan' from 'SpanMark'.
484+spanMarkSpan :: SpanMark -> RealSrcSpan
485+spanMarkSpan = \case
486+ HaddockSpan _ s -> s
487+ CommentSpan s -> s
488+ OtherSpan s -> s
489+
466490-- | Haddock string style.
467491data HaddockStyle
468492 = -- | @-- |@
@@ -475,20 +499,18 @@ data HaddockStyle
475499 Named String
476500
477501-- | Set span of last output comment.
478−setLastCommentSpan ::
479− -- | 'HaddockStyle' or 'Nothing' if it's a non-Haddock comment
480− Maybe HaddockStyle ->
481− -- | Location of last printed comment
482− RealSrcSpan ->
502+setSpanMark ::
503+ -- | Span mark to set
504+ SpanMark ->
483505 R ()
484−setLastCommentSpan mhStyle spn = R . modify $ \sc ->
506+setSpanMark spnMark = R . modify $ \sc ->
485507 sc
486− { scLastCommentSpan = Just (mhStyle, spn)
508+ { scSpanMark = Just spnMark
487509 }
488510
489511-- | Get span of last output comment.
490−getLastCommentSpan :: R (Maybe (Maybe HaddockStyle, RealSrcSpan))
491−getLastCommentSpan = R (gets scLastCommentSpan)
512+getSpanMark :: R (Maybe SpanMark)
513+getSpanMark = R (gets scSpanMark)
492514
493515----------------------------------------------------------------------------
494516-- Annotations
src/Ormolu/Printer/Meat/Common.hs+6−4
@@ -17,7 +17,6 @@ where
1717
1818import Control.Monad
1919import Data.List (isPrefixOf)
20−import Data.Maybe (isJust)
2120import qualified Data.Text as T
2221import GHC hiding (GhcPs, IE)
2322import Name (nameStableString)
@@ -155,7 +154,10 @@ p_hsDocString ::
155154 LHsDocString ->
156155 R ()
157156p_hsDocString hstyle needsNewline (L l str) = do
158− goesAfterComment <- isJust <$> getLastCommentSpan
157+ goesAfterComment <- getSpanMark >>= \case
158+ Just (HaddockSpan _ _) -> return True
159+ Just (CommentSpan _) -> return True
160+ _ -> return False
159161 -- Make sure the Haddock is separated by a newline from other comments.
160162 when goesAfterComment newline
161163 forM_ (zip (splitDocString str) (True : repeat False)) $ \(x, isFirst) -> do
@@ -174,8 +176,8 @@ p_hsDocString hstyle needsNewline (L l str) = do
174176 -- It's often the case that the comment itself doesn't have a span
175177 -- attached to it and instead its location can be obtained from
176178 -- nearest enclosing span.
177− getEnclosingSpan (const True) >>= mapM_ (setLastCommentSpan (Just hstyle))
178− RealSrcSpan spn -> setLastCommentSpan (Just hstyle) spn
179+ getEnclosingSpan (const True) >>= mapM_ (setSpanMark . HaddockSpan hstyle)
180+ RealSrcSpan spn -> setSpanMark (HaddockSpan hstyle spn)
179181
180182-- | Print anchor of named doc section.
181183p_hsDocName :: String -> R ()
src/Ormolu/Printer/Meat/Declaration/Value.hs+25−3
@@ -345,7 +345,7 @@ p_hsCmd = \case
345345 txt "do"
346346 newline
347347 inci . located es $
348− sitcc . sep newline (located' (sitcc . p_stmt' cmdPlacement p_hsCmd))
348+ sitcc . sep newline (sitcc . withSpacing (p_stmt' cmdPlacement p_hsCmd))
349349 HsCmdWrap {} -> notImplemented "HsCmdWrap"
350350 XCmd x -> noExtCon x
351351
@@ -354,6 +354,28 @@ p_hsCmdTop = \case
354354 HsCmdTop NoExtField cmd -> located cmd p_hsCmd
355355 XCmdTop x -> noExtCon x
356356
357+withSpacing :: Data a => (a -> R ()) -> Located a -> R ()
358+withSpacing f l = located l $ \x -> do
359+ case getLoc l of
360+ UnhelpfulSpan _ -> f x
361+ RealSrcSpan currentSpn -> do
362+ getSpanMark >>= \case
363+ -- Spacing before comments will be handled by the code
364+ -- that prints comments, so we just have to deal with
365+ -- blank lines between statements here.
366+ Just (OtherSpan lastSpn) ->
367+ if srcSpanStartLine currentSpn > srcSpanEndLine lastSpn + 1
368+ then newline
369+ else return ()
370+ _ -> return ()
371+ f x
372+ -- In some cases the (f x) expression may insert a new mark. We want
373+ -- to be careful not to override comment marks.
374+ getSpanMark >>= \case
375+ Just (HaddockSpan _ _) -> return ()
376+ Just (CommentSpan _) -> return ()
377+ _ -> setSpanMark (OtherSpan currentSpn)
378+
357379p_stmt :: Stmt GhcPs (LHsExpr GhcPs) -> R ()
358380p_stmt = p_stmt' exprPlacement p_hsExpr
359381
@@ -425,7 +447,7 @@ p_stmt' placer render = \case
425447 RecStmt {..} -> do
426448 txt "rec"
427449 space
428− sitcc $ sepSemi (located' (p_stmt' placer render)) recS_stmts
450+ sitcc $ sepSemi (withSpacing (p_stmt' placer render)) recS_stmts
429451 XStmtLR c -> noExtCon c
430452
431453gatherStmt :: ExprLStmt GhcPs -> [[ExprLStmt GhcPs]]
@@ -647,7 +669,7 @@ p_hsExpr' s = \case
647669 ub <- layoutToBraces <$> getLayout
648670 inci $
649671 sepSemi
650− (located' (ub . p_stmt' exprPlacement (p_hsExpr' S)))
672+ (ub . withSpacing (p_stmt' exprPlacement (p_hsExpr' S)))
651673 (unLoc es)
652674 compBody = brackets N $ located es $ \xs -> do
653675 let p_parBody =