1
1
mirror of https://github.com/google/ormolu.git synced 2024-11-27 13:13:23 +03:00

Implement handling of semicolons

This commit is contained in:
Utku Demir 2019-08-12 15:37:25 +12:00 committed by Mark Karpov
parent c3d69d5853
commit 16f2c644a7
32 changed files with 288 additions and 139 deletions

View File

@ -1,8 +1,6 @@
{-# LANGUAGE TypeFamilies #-}
class Foo a where
data FooBar a
class Foo a where data FooBar a
-- | Something.
class Bar a where

View File

@ -1,8 +1,6 @@
{-# LANGUAGE TypeFamilies #-}
class Foo a where
type FooBar a = Int
class Foo a where type FooBar a = Int
-- | Something.
class Bar a where

View File

@ -1,8 +1,6 @@
{-# LANGUAGE TypeFamilies #-}
class Foo a where
type FooBar a
class Foo a where type FooBar a
-- | Something.
class Bar a where

View File

@ -3,9 +3,7 @@
-- | Something.
class Foo a b | a -> b
class Bar a b | a -> b, b -> a where
bar :: a
class Bar a b | a -> b, b -> a where bar :: a
-- | Something else.
class

View File

@ -1,8 +1,6 @@
{-# LANGUAGE MultiParamTypeClasses #-}
class Foo a b where
foo :: a -> b
class Foo a b where foo :: a -> b
-- | Something.
class Bar a b c d where

View File

@ -1,7 +1,5 @@
-- | Something.
class Foo a where
foo :: a
class Foo a where foo :: a
-- | Something more.
class Bar a where

View File

@ -1,8 +1,6 @@
{-# LANGUAGE TypeFamilies #-}
instance Foo Int where
data Bar Int = IntBar Int Int
instance Foo Int where data Bar Int = IntBar Int Int
instance Foo Double where

View File

@ -1,8 +1,6 @@
{-# LANGUAGE TypeFamilies #-}
instance Foo Int where
type Bar Int = Double
instance Foo Int where type Bar Int = Double
instance Foo Double where

View File

@ -1,6 +1,4 @@
instance Eq a => Eq [a] where
(==) _ _ = False
instance Eq a => Eq [a] where (==) _ _ = False
instance
( Ord a,

View File

@ -1,6 +1,4 @@
instance MonadReader a ((->) a) where
ask = id
instance MonadReader a ((->) a) where ask = id
instance MonadState s (State s) where

View File

@ -1,6 +1,4 @@
instance {-# OVERLAPPABLE #-} Eq Int where
(==) _ _ = False
instance {-# OVERLAPPABLE #-} Eq Int where (==) _ _ = False
instance {-# OVERLAPPING #-} Ord Int where

View File

@ -1,6 +1,4 @@
instance Monoid Int where
(<>) x y = x + y
instance Monoid Int where (<>) x y = x + y
instance Enum Int where
@ -9,3 +7,5 @@ instance Enum Int where
toEnum =
\x ->
x
instance Foo Int where foo x = x; bar y = y

View File

@ -6,3 +6,5 @@ instance Enum Int
toEnum
= \x ->
x
instance Foo Int where { foo x = x; bar y = y }

View File

@ -1,2 +1,7 @@
foo :: Int -> Int
foo x = case x of x -> x
foo :: IO ()
foo = case [1] of [_] -> "singleton"; _ -> "not singleton"
foo = case [1] of { [] -> foo; _ -> bar } `finally` baz

View File

@ -1,2 +1,6 @@
foo :: Int -> Int
foo x = case x of x -> x
foo :: IO ()
foo = case [1] of [_] -> "singleton"; _ -> "not singleton"
foo = case [1] of { [] -> foo; _ -> bar } `finally` baz

View File

@ -1,8 +1,6 @@
{-# LANGUAGE RecursiveDo #-}
bar = do
foo
bar
bar = do foo; bar
baz =
mdo
@ -60,15 +58,22 @@ g = unFoo
. foo
bar
baz
3 $ do
act
ret
3
$ do
act
ret
main =
do stuff
`finally` do
recover
main = do stuff `finally` recover
main = do { stuff } `finally` recover
foo = do do { foo; bar }; baz
foo =
do
1
@ -77,5 +82,30 @@ foo =
-- single line let-where
samples n f = do
gen <- newQCGen
let rands g = g1 : rands g2 where (g1, g2) = split g
let rands g = g1 : rands g2 where { (g1, g2) = split g }
return $ rands gen
main = do bar
main = do bar; baz
main = do
bar
baz
main = do
a <- bar
let a = b; c = d
baz d
let e = f
g = h
return c
readInClause = do
do
lookAhead g_Do
parseNote ErrorC 1063 "You need a line feed or semicolon before the 'do'."
<|> do
optional g_Semi
void allspacing
return things

View File

@ -67,6 +67,12 @@ main =
`finally` do
recover
main = do stuff `finally` recover
main = do { stuff } `finally` recover
foo = do { do {foo; bar}; baz }
foo = do
1
+
@ -77,3 +83,29 @@ samples n f = do
gen <- newQCGen
let rands g = g1 : rands g2 where (g1, g2) = split g
return $ rands gen
main = do bar
main = do { bar; baz }
main = do { bar
; baz
}
main = do
a <- bar
let a = b; c = d
baz d
let e = f
g = h
return c
readInClause = do
do {
lookAhead g_Do;
parseNote ErrorC 1063 "You need a line feed or semicolon before the 'do'.";
} <|> do {
optional g_Semi;
void allspacing;
}
return things

View File

@ -20,3 +20,8 @@ tricky2 =
flip all (zip ws gs)
$ \(wt, gt) ->
canUnify poly_given_ok wt gt || go False wt gt
foo =
prop "is inverse to closure" $ \(f :: StaticPtr (Int -> Int))
(x :: Int) ->
(unclosure . closure) f x == deRefStaticPtr f x

View File

@ -20,3 +20,9 @@ tricky2 =
flip all (zip ws gs) $
\(wt, gt) ->
canUnify poly_given_ok wt gt || go False wt gt
foo =
prop "is inverse to closure" $ \(f :: StaticPtr (Int -> Int))
(x :: Int) ->
(unclosure . closure) f x == deRefStaticPtr f x

View File

@ -1,3 +1,9 @@
foo :: a -> a
foo x = let x = x in x
foo x = let x = z where z = 2 in x
foo x = let x = z where { z = 2 } in x
foo x = let x = z where { z = 2 }; a = 3 in x
foo x = let g :: Int -> Int; g = id in ()
let a = b; c = do { foo; bar }; d = baz in b
let a = case True of { True -> foo; False -> bar }; b = foo a in b

View File

@ -1,3 +1,8 @@
foo :: a -> a
foo x = let x = x in x
foo x = let x = z where z = 2 in x
foo x = let x = z where { z = 2; }; a = 3 in x
foo x = let {g :: Int -> Int; g = id} in ()
let a = b; c = do { foo; bar }; d = baz in b
let a = case True of { True -> foo; False -> bar }; b = foo a in b

View File

@ -38,7 +38,8 @@ lenses =
.= ("user.connection" :: Text)
# "connection"
.= uc
# "user" .= case name of
Just n -> Just $ object ["name" .= n]
Nothing -> Nothing
# "user"
.= case name of
Just n -> Just $ object ["name" .= n]
Nothing -> Nothing
# []

View File

@ -1,6 +1,9 @@
foo :: Int
foo = bar
where
Foo bar baz = quux
Baz
quux = zoo
foo = bar
where
Foo bar baz = quux

View File

@ -1,6 +1,9 @@
foo :: Int
foo = bar
where
Foo bar baz = quux
Baz
quux = zoo
foo = bar
where Foo bar baz = quux

View File

@ -24,6 +24,9 @@ module Ormolu.Printer.Combinators
, breakpoint'
-- ** Formatting lists
, sep
, sepSemi
, useBraces
, dontUseBraces
-- ** Wrapping
, sitcc
, line
@ -42,6 +45,7 @@ module Ormolu.Printer.Combinators
)
where
import Control.Monad
import Data.Data (Data)
import Data.List (intersperse)
import Data.Text (Text)
@ -156,6 +160,37 @@ sep
-> R ()
sep s f xs = sequence_ (intersperse s (f <$> xs))
-- | Render a collection of elements layout-sensitively using given printer,
-- inserting semicolons if necessary and respecting 'useBraces' and
-- 'dontUseBraces' combinators.
--
-- > useBraces $ sepSemi txt ["foo", "bar"]
-- > == vlayout (txt "{ foo; bar }") (txt "foo\nbar")
--
-- > dontUseBraces $ sepSemi txt ["foo", "bar"]
-- > == vlayout (txt "foo; bar") (txt "foo\nbar")
sepSemi
:: (a -> R ()) -- ^ How to render an element
-> [a] -- ^ Elements to render
-> R ()
sepSemi f xs = vlayout singleLine multiLine
where
singleLine = do
ub <- canUseBraces
case xs of
[] -> when ub $ txt "{}"
xs' ->
if ub
then do
txt "{ "
sep (txt "; ") (dontUseBraces . f) xs'
txt " }"
else
sep (txt "; ") f xs'
multiLine =
sep newline (dontUseBraces . f) xs
----------------------------------------------------------------------------
-- Wrapping

View File

@ -23,6 +23,10 @@ module Ormolu.Printer.Internal
, Layout (..)
, enterLayout
, vlayout
-- * Helpers for braces
, useBraces
, dontUseBraces
, canUseBraces
-- * Special helpers for comment placement
, trimSpanStream
, nextEltSpan
@ -75,6 +79,8 @@ data RC = RC
-- ^ Span of enclosing element of AST
, rcAnns :: Anns
-- ^ Collection of annotations
, rcCanUseBraces :: Bool
-- ^ If the last expression in the layout can use braces
}
-- | State context of 'R'.
@ -117,6 +123,7 @@ runR debug (R m) sstream cstream anns =
, rcDebug = debug
, rcEnclosingSpans = []
, rcAnns = anns
, rcCanUseBraces = False
}
sc = SC
{ scColumn = 0
@ -344,6 +351,24 @@ getAnns
-> R [AnnKeywordId]
getAnns spn = lookupAnns spn <$> R (asks rcAnns)
----------------------------------------------------------------------------
-- Helpers for braces
-- | Make the inner computation use braces around single-line layouts.
useBraces :: R () -> R ()
useBraces (R r) = R (local (\i -> i {rcCanUseBraces = True}) r)
-- | Make the inner computation omit braces around single-line layouts.
dontUseBraces :: R () -> R ()
dontUseBraces (R r) = R (local (\i -> i {rcCanUseBraces = False}) r)
-- | Return 'True' if we can use braces in this context.
canUseBraces :: R Bool
canUseBraces = R $ asks rcCanUseBraces
----------------------------------------------------------------------------
-- Debug helpers

View File

@ -1,4 +1,5 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
@ -10,7 +11,6 @@ module Ormolu.Printer.Meat.Declaration
)
where
import Control.Monad (forM_)
import GHC
import OccName (occNameFS)
import Ormolu.Printer.Combinators
@ -34,16 +34,17 @@ import Ormolu.Utils
import RdrName (rdrNameOcc)
p_hsDecls :: FamilyStyle -> [LHsDecl GhcPs] -> R ()
p_hsDecls style decls =
forM_ (zip decls ((Just <$> drop 1 decls) ++ [Nothing])) $ \(d, md) ->
case md of
Nothing -> located d pDecl
Just d' ->
line $ if separatedDecls (unLoc d) (unLoc d')
then line (located d pDecl)
else located d pDecl
p_hsDecls style decls = do
sepSemi (\(x, r) -> located x pDecl >> r) (separated decls)
where
pDecl = p_hsDecl style
pDecl = dontUseBraces . p_hsDecl style
separated [] = []
separated [x] = [(x, return ())]
separated (x:y:xs) =
if separatedDecls (unLoc x) (unLoc y)
then (x, breakpoint') : separated (y:xs)
else (x, return ()) : separated (y:xs)
p_hsDecl :: FamilyStyle -> HsDecl GhcPs -> R ()
p_hsDecl style = \case

View File

@ -66,8 +66,8 @@ p_classDecl ctx name tvars fixity fdeps csigs cdefs cats catdefs = do
snd <$> sortBy (comparing fst) (sigs <> vals <> tyFams <> tyFamDefs)
unless (null allDecls) $ do
txt " where"
newline -- Ensure line is added after where clause.
newline -- Add newline before first declaration.
breakpoint -- Ensure whitespace is added after where clause.
breakpoint' -- Add newline before first declaration.
inci (p_hsDecls Associated allDecls)
p_classContext :: LHsContext GhcPs -> R ()

View File

@ -52,7 +52,7 @@ p_dataDecl style name tpats fixity HsDataDefn {..} = do
then do
txt " where"
breakpoint
inci . sitcc $ sep newline (sitcc . located' p_conDecl) dd_cons
inci $ sepSemi (located' p_conDecl) dd_cons
else switchLayout (getLoc name : (getLoc <$> dd_cons)) $
inci $ do
breakpoint

View File

@ -67,11 +67,7 @@ p_clsInstDecl = \case
ClsInstDecl {..} -> do
txt "instance"
case cid_poly_ty of
HsIB {..} -> located hsib_body $ \x -> do
breakpoint
inci $ do
match_overlap_mode cid_overlap_mode breakpoint
p_hsType x
HsIB {..} -> do
-- GHC's AST does not necessarily store each kind of element in source
-- location order. This happens because different declarations are stored in
-- different lists. Consequently, to get all the declarations in proper
@ -87,13 +83,19 @@ p_clsInstDecl = \case
allDecls =
snd <$>
sortBy (comparing fst) (sigs <> vals <> tyFamInsts <> dataFamInsts)
unless (null allDecls) $ do
switchLayout [getLoc hsib_body] breakpoint
located hsib_body $ \x -> do
breakpoint
inci $ do
txt "where"
newline -- Ensure line is added after where clause.
newline -- Add newline before first declaration.
p_hsDecls Associated allDecls
match_overlap_mode cid_overlap_mode breakpoint
p_hsType x
unless (null allDecls) $ do
breakpoint
txt "where"
unless (null allDecls) $ do
inci $ do
breakpoint -- Ensure whitespace is added after where clause.
breakpoint' -- Add newline before first declaration
dontUseBraces $ p_hsDecls Associated allDecls
XHsImplicitBndrs NoExt -> notImplemented "XHsImplicitBndrs"
XClsInstDecl NoExt -> notImplemented "XClsInstDecl"

View File

@ -81,8 +81,15 @@ p_matchGroup'
-> MatchGroupStyle
-> MatchGroup GhcPs (Located body)
-> R ()
p_matchGroup' placer pretty style MG {..} =
sep newline (located' p_Match) (unLoc mg_alts)
p_matchGroup' placer pretty style MG {..} = do
let ob = case style of
Case -> id
LambdaCase -> id
_ -> dontUseBraces
-- NOTE since we are forcing braces on 'sepSemi' based on 'ob', we have to
-- restore the brace state inside the sepsemi.
ub <- bool dontUseBraces useBraces <$> canUseBraces
ob $ sepSemi (located' (ub . p_Match)) (unLoc mg_alts)
where
p_Match m@Match {..} =
p_match'
@ -179,7 +186,6 @@ p_match' placer pretty style isInfix strictness m_pats m_grhss = do
stdCase
LambdaCase -> stdCase
return inci'
let
-- Calculate position of end of patterns. This is useful when we decide
-- about putting certain constructions in hanging positions.
@ -192,49 +198,44 @@ p_match' placer pretty style isInfix strictness m_pats m_grhss = do
Case -> True
LambdaCase -> True
_ -> False
let GRHSs {..} = m_grhss
hasGuards = withGuards grhssGRHSs
unless (length grhssGRHSs > 1) $ do
case style of
Function _ | hasGuards -> return ()
Function _ -> txt " ="
PatternBind -> txt " ="
s | isCase s && hasGuards -> return ()
_ -> txt " ->"
let grhssSpan = combineSrcSpans' $
getGRHSSpan . unLoc <$> NE.fromList grhssGRHSs
patGrhssSpan = maybe grhssSpan
(combineSrcSpans grhssSpan . srcLocSpan) endOfPats
placement =
case endOfPats of
Nothing -> blockPlacement placer grhssGRHSs
Just spn ->
if isOneLineSpan
(mkSrcSpan spn (srcSpanStart grhssSpan))
then blockPlacement placer grhssGRHSs
else Normal
p_body = do
let groupStyle =
if isCase style && hasGuards
then RightArrow
else EqualSign
sep newline (located' (p_grhs' pretty groupStyle)) grhssGRHSs
p_where = do
let whereIsEmpty = GHC.isEmptyLocalBindsPR (unLoc grhssLocalBinds)
unless (GHC.eqEmptyLocalBinds (unLoc grhssLocalBinds)) $ do
breakpoint
txt "where"
unless whereIsEmpty breakpoint
inci $ located grhssLocalBinds p_hsLocalBinds
inci' $ do
let GRHSs {..} = m_grhss
hasGuards = withGuards grhssGRHSs
unless (length grhssGRHSs > 1) $ do
case style of
Function _ | hasGuards -> return ()
Function _ -> txt " ="
PatternBind -> txt " ="
s | isCase s && hasGuards -> return ()
_ -> txt " ->"
let grhssSpan = combineSrcSpans' $
getGRHSSpan . unLoc <$> NE.fromList grhssGRHSs
patGrhssSpan = maybe grhssSpan
(combineSrcSpans grhssSpan . srcLocSpan) endOfPats
placement =
case endOfPats of
Nothing -> blockPlacement placer grhssGRHSs
Just spn ->
if isOneLineSpan
(mkSrcSpan spn (srcSpanStart grhssSpan))
then blockPlacement placer grhssGRHSs
else Normal
inciLocalBinds = case placement of
Normal -> id
Hanging -> inci
p_body = do
let groupStyle =
if isCase style && hasGuards
then RightArrow
else EqualSign
sep newline (located' (p_grhs' pretty groupStyle)) grhssGRHSs
let whereLocation = combineSrcSpans patGrhssSpan $ getLoc grhssLocalBinds
whereIsEmpty = GHC.isEmptyLocalBindsPR (unLoc grhssLocalBinds)
unless (GHC.eqEmptyLocalBinds (unLoc grhssLocalBinds))
. inciLocalBinds
. switchLayout [whereLocation] $ do
if whereIsEmpty then newline else breakpoint
txt "where"
unless whereIsEmpty $ do
breakpoint
inci (located grhssLocalBinds p_hsLocalBinds)
switchLayout [patGrhssSpan] $
placeHanging placement p_body
inci p_where
p_grhs :: GroupStyle -> GRHS GhcPs (LHsExpr GhcPs) -> R ()
p_grhs = p_grhs' p_hsExpr
@ -386,7 +387,7 @@ p_stmt' pretty = \case
inci (p_hsExpr x)
RecStmt {..} -> do
txt "rec "
sitcc $ sep newline (located' (p_stmt' pretty)) recS_stmts
sitcc $ sepSemi (located' (p_stmt' pretty)) recS_stmts
XStmtLR {} -> notImplemented "XStmtLR"
gatherStmt :: ExprLStmt GhcPs -> [[ExprLStmt GhcPs]]
@ -411,7 +412,7 @@ p_hsLocalBinds = \case
(Left <$> bagToList bag) ++ (Right <$> lsigs)
p_item (Left x) = located x p_valDecl
p_item (Right x) = located x p_sigDecl
sitcc $ sep newline (sitcc . p_item) (sortOn ssStart items)
sitcc $ sepSemi (useBraces . p_item) (sortOn ssStart items)
HsValBinds NoExt _ -> notImplemented "HsValBinds"
HsIPBinds NoExt _ -> notImplemented "HsIPBinds"
EmptyLocalBinds NoExt -> return ()
@ -468,18 +469,23 @@ p_hsExpr = \case
txt "@"
located (hswc_body a) p_hsType
OpApp NoExt x op y -> do
located x p_hsExpr
-- NOTE If the end of the first argument and the beginning of the second
-- argument are on the same line, and the second argument has a hanging
-- form, use hanging placement.
-- NOTE If the beginning of the first argument and the second argument
-- are on the same line, and the second argument has a hanging form, use
-- hanging placement.
let placement =
if isOneLineSpan
(mkSrcSpan (srcSpanEnd (getLoc x)) (srcSpanStart (getLoc y)))
(mkSrcSpan (srcSpanStart (getLoc x)) (srcSpanStart (getLoc y)))
then exprPlacement (unLoc y)
else Normal
opWrapper = case unLoc op of
EWildPat NoExt -> backticks
_ -> id
ub <- vlayout
(return useBraces)
(return $ case placement of
Hanging -> useBraces
Normal -> dontUseBraces)
ub $ located x p_hsExpr
placeHanging placement $ do
located op (opWrapper . p_hsExpr)
space
@ -543,10 +549,9 @@ p_hsExpr = \case
HsDo NoExt ctx es -> do
let doBody header = do
txt header
if length (unLoc es) <= 1
then breakpoint
else newline
inci $ located es (sep newline (located' (sitcc . p_stmt)))
breakpoint
ub <- vlayout (return useBraces) (return id)
inci $ sepSemi (located' (ub . p_stmt)) (unLoc es)
compBody = brackets $ located es $ \xs -> do
let p_parBody = sitcc . sep
(breakpoint >> txt "| ")
@ -864,7 +869,7 @@ p_hsBracket = \case
txt "|"
breakpoint'
inci $ do
body
dontUseBraces body
breakpoint'
txt "|]"

View File

@ -57,14 +57,15 @@ p_hsModule pragmas (L moduleSpan HsModule {..}) = do
when (hasImports || hasDecls) newline
forM_ (sortImports hsmodImports) (located' p_hsmodImport)
when (hasImports && hasDecls) newline
p_hsDecls Free hsmodDecls
trailingComments <- hasMoreComments
when hasDecls $ do
newlineModified <- isNewlineModified
newline
-- In this case we need to insert a newline between the comments
-- output as a side effect of the previous newline and trailing
-- comments to prevent them from merging.
when (newlineModified && trailingComments) newline
when (trailingComments && hasModuleHeader) newline
spitRemainingComments
switchLayout (map getLoc hsmodDecls) $ do
p_hsDecls Free hsmodDecls
trailingComments <- hasMoreComments
when hasDecls $ do
newlineModified <- isNewlineModified
newline
-- In this case we need to insert a newline between the comments
-- output as a side effect of the previous newline and trailing
-- comments to prevent them from merging.
when (newlineModified && trailingComments) newline
when (trailingComments && hasModuleHeader) newline
spitRemainingComments