{-|
Module      : Language.Rust.Pretty.Internal
Description : Rust pretty printer
Copyright   : (c) Alec Theriault, 2017-2018
License     : BSD-style
Maintainer  : alec.theriault@gmail.com
Stability   : experimental
Portability : portable

The pretty printing facilities in this file are re-exported to 'Language.Rust.Pretty' via the
'Pretty' and 'PrettyAnnotated' classes. There may be invariants in this module that are not properly
documented. 
-}
{-# OPTIONS_GHC -Wall -fno-warn-name-shadowing #-}
{-# OPTIONS_HADDOCK hide, not-home #-}
{-# LANGUAGE OverloadedStrings #-}

module Language.Rust.Pretty.Internal (

  -- * Pretty printers
  -- As much as possible, these functions should say which function in the @rustc@ printer they are
  -- emulating.

  -- ** Top level
  printSourceFile,
  
  -- ** General
  printMutability,
  printUnsafety,
  printFnArgsAndRet,
  printIdent,
  
  -- ** Paths
  printPath,
  
  -- ** Attributes
  printAttr,
  
  -- ** Literals
  printLit,
  printLitSuffix,
  printLitTok,
  
  -- ** Expressions
  printExpr,
  printAbi,
  printUnOp,
  printBinOp,
  printField,
  printRangeLimits,
 
  -- ** Types and lifetimes
  printType,
  printGenerics,
  printLifetime,
  printLifetimeDef,
  printTyParam,
  printBound,
  printWhereClause,
  printWherePredicate,
  printPolyTraitRef,
  printTraitRef,
  
  -- ** Patterns
  printPat,
  printBindingMode,
  printFieldPat,
  
  -- ** Statements
  printStmt,
  
  -- ** Items
  printItem,
  printForeignItem,
  printImplItem,
  printTraitItem,
  printPolarity,
  printStructField,
  printVariant,
  printUseTree,
  printVis,
  
  -- ** Blocks
  printBlock,

  -- ** Token trees
  printToken,
  printTt,
  printTokenStream,
  printNonterminal,
  printMac,

  -- * Utilities
  module Language.Rust.Pretty.Util,
) where

import Language.Rust.Pretty.Literals
import Language.Rust.Pretty.Util

import Language.Rust.Data.Ident
import Language.Rust.Data.Position

import Language.Rust.Syntax.AST
import Language.Rust.Syntax.Token

import Data.Text.Prettyprint.Doc hiding ( (<+>), hsep, indent, vsep )

import Data.Maybe               ( maybeToList, fromMaybe )
import Data.Foldable            ( toList )
import Data.List                ( unfoldr )
import qualified Data.List.NonEmpty as N

-- | Print a source file
printSourceFile :: SourceFile a -> Doc a
printSourceFile (SourceFile shebang as items) = foldr1 (\x y -> x <#> "" <#> y) ls
  where ls = concat [ maybeToList ((\s -> "#!" <> pretty s) <$> shebang)
                    , [ vcat [ printAttr a False | a <- as ] ]
                    , punctuate line' (printItem `map` items)
                    ]

-- | Print a name
printName :: Name -> Doc a
printName = pretty

-- | Print an identifier
printIdent :: Ident -> Doc a
printIdent (Ident s False _) = pretty s
printIdent (Ident s True _) = "r#" <> pretty s

-- | Print a type (@print_type@ with @print_ty_fn@ inlined)
-- Types are expected to always be only one line
printType :: Ty a -> Doc a
printType (Slice ty x)          = annotate x ("[" <> printType ty <> "]")
printType (Array ty v x)        = annotate x ("[" <> printType ty <> ";" <+> printExpr v <> "]")
printType (Ptr mut ty x)        = annotate x ("*" <> printFullMutability mut <+> printType ty)
printType (Rptr lt mut ty x)    = annotate x ("&" <> perhaps printLifetime lt <+> printMutability mut <+> printType ty)
printType (Never x)             = annotate x "!"
printType (TupTy [elt] x)       = annotate x (block Paren True ""  mempty [ printType elt <> "," ])
printType (TupTy elts x)        = annotate x (block Paren True "," mempty (printType `map` elts))
printType (PathTy Nothing p x)  = annotate x (printPath p False)
printType (PathTy (Just q) p x) = annotate x (printQPath p q False)
printType (TraitObject bs x)    = let prefix = if null (N.tail bs) then "dyn" else mempty
                                  in annotate x (printBounds prefix (toList bs))
printType (ImplTrait bs x)      = annotate x (printBounds "impl" (toList bs))
printType (ParenTy ty x)        = annotate x ("(" <> printType ty <> ")")
printType (Typeof e x)          = annotate x ("typeof" <> block Paren True mempty mempty [ printExpr e ])
printType (Infer x)             = annotate x "_"
printType (MacTy m x)           = annotate x (printMac Bracket m)
printType (BareFn u a l d x)    = annotate x (printFormalLifetimeList l
                                               <+> printFnHeaderInfo u NotConst a InheritedV
                                               <> printFnArgsAndRet d)

-- | Print a macro (@print_mac@)
printMac :: Delim -> Mac a -> Doc a
printMac d (Mac path ts x) = annotate x (printPath path False <> "!" <> body)
  where body = block d True mempty mempty [ printTokenStream ts ]

-- | Given two positions, find out how to print appropriate amounts of space between them
printSpaceBetween :: Bool -> Span -> Span -> Maybe (Doc a)
printSpaceBetween spaceNeeded (Span _ (Position _ y1 x1)) (Span (Position _ y2 x2) _)
  | y2 == y1 && x2 > x1 = Just $ hcat (replicate (x2 - x1) space)
  | y2 > y1             = Just $ hcat (replicate (y2 - y1) line) <> column (\x1' -> hcat (replicate (x2 - x1') space))
  | spaceNeeded         = Just space 
  | otherwise           = Just mempty
printSpaceBetween _ _ _ = Nothing 

-- | Print a token tree (@print_tt@)
printTt :: TokenTree -> Doc a
printTt (Token _ t) = printToken t
printTt (Delimited _ d ts) = block d True mempty mempty [ printTokenStream ts ] 

-- | Print a list of token trees, with the right amount of space between successive elements
printTokenTrees :: [TokenTree] -> Doc a
printTokenTrees [] = mempty
printTokenTrees [tt] = printTt tt
printTokenTrees (tt1:tt2:tts) = printTt tt1 <> sp <> printTokenTrees (tt2:tts)
  where
  extremities :: TokenTree -> (Token, Token)
  extremities (Token _ t ) = (t,t)
  extremities (Delimited _ d _) = (OpenDelim d, CloseDelim d)

  -- extremities in this particular situation
  (lastTt1, firstTt2) = (snd $ extremities tt1, fst $ extremities tt2)
  spNeeded = lastTt1 `spaceNeeded` firstTt2

  -- Spacing of tokens, as informed by positional information on tokens
  spPos = printSpaceBetween spNeeded (spanOf tt1) (spanOf tt2)

  -- Spacing of tokens, as informed by 'spaceNeeded' and special cases
  spTok = case (snd $ extremities tt1, fst $ extremities tt2) of
            (Comma, _) -> space
            (Colon, _) -> space
            (Semicolon, _) -> space
            (_, OpenDelim Brace) -> space
            (CloseDelim Brace, _) -> space
            (t1, t2) | t1 `elem` toksRequiringSp || t2 `elem` toksRequiringSp -> space 
                     | otherwise -> if spNeeded then space else mempty

  -- List of tokens that want to have space on either side of them
  toksRequiringSp = [ Equal, GreaterEqual, GreaterGreaterEqual, EqualEqual, NotEqual, LessEqual,
                      LessLessEqual, MinusEqual, AmpersandEqual, PipeEqual, PlusEqual, StarEqual,
                      SlashEqual, CaretEqual, PercentEqual, RArrow, LArrow, FatArrow ]
  
  -- Use 'spPos' with 'spTok' as a fallback
  sp = fromMaybe spTok spPos

-- | Print the space between a token stream and the mod-path before it
printTokenStreamSp :: TokenStream -> Doc a
printTokenStreamSp (Tree Token{}) = space
printTokenStreamSp (Tree Delimited{}) = mempty
printTokenStreamSp (Stream []) = mempty
printTokenStreamSp (Stream (t:_)) = printTokenStreamSp t

-- | Print a token stream
printTokenStream :: TokenStream -> Doc a
printTokenStream = printTokenTrees . unfoldr unconsTokenStream

-- | Print a token (@token_to_string@)
-- Single character expression-operator symbols.
printToken :: Token -> Doc a
printToken Equal = "="
printToken Less = "<"
printToken Greater = ">"
printToken Ampersand = "&"
printToken Pipe = "|"
printToken Exclamation = "!"
printToken Tilde = "~"
printToken Plus = "+"
printToken Minus = "-"
printToken Star = "*"
printToken Slash = "/"
printToken Percent = "%"
-- Multi character eexpression-operator symbols
printToken GreaterEqual = ">="
printToken GreaterGreaterEqual = ">>="
printToken AmpersandAmpersand = "&&"
printToken PipePipe = "||"
printToken LessLess = "<<"
printToken GreaterGreater = ">>"
printToken EqualEqual = "=="
printToken NotEqual = "!="
printToken LessEqual = "<="
printToken LessLessEqual = "<<="
printToken MinusEqual = "-="
printToken AmpersandEqual = "&="
printToken PipeEqual = "|="
printToken PlusEqual = "+="
printToken StarEqual = "*="
printToken SlashEqual = "/="
printToken CaretEqual = "^="
printToken PercentEqual = "%="
printToken Caret = "^"
-- Structural symbols
printToken At = "@"
printToken Dot = "."
printToken DotDot = ".."
printToken DotDotDot = "..."
printToken DotDotEqual = "..="
printToken Comma = ","
printToken Semicolon = ";"
printToken Colon = ":"
printToken ModSep = "::"
printToken RArrow = "->"
printToken LArrow = "<-"
printToken FatArrow = "=>"
printToken Pound = "#"
printToken Dollar = "$"
printToken Question = "?"
-- Delimiters, eg. @{@, @]@, @(@
printToken (OpenDelim Paren) = "("
printToken (OpenDelim Bracket) = "["
printToken (OpenDelim Brace) = "{"
printToken (OpenDelim NoDelim) = ""
printToken (CloseDelim Paren) = ")"
printToken (CloseDelim Bracket) = "]"
printToken (CloseDelim Brace) = "}"
printToken (CloseDelim NoDelim) = ""
-- Literals
printToken (LiteralTok l s) = noIndent $ printLitTok l <> perhaps printName s
-- Name components
printToken (IdentTok i) = printIdent i
printToken (LifetimeTok i) = "'" <> printIdent i
printToken (Space Whitespace _) = " "
printToken (Space Comment n) = "/*" <> printName n <> " */"
printToken (Doc d Inner True) = "/*!" <> printName d <> "*/"
printToken (Doc d Outer True) = "/**" <> printName d <> "*/"
printToken (Doc d Inner False) = "//!" <> printName d
printToken (Doc d Outer False) = "///" <> printName d
printToken Shebang = "#!"
-- Macro related 
printToken (Interpolated n) = unAnnotate (printNonterminal n)
-- Other
printToken t = error $ "printToken: " ++ show t


-- | Print a literal token
printLitTok :: LitTok -> Doc a
printLitTok (ByteTok n)         = "b'" <> printName n <> "'"
printLitTok (CharTok n)         = "'" <> printName n <> "'"
printLitTok (IntegerTok n)      = printName n
printLitTok (FloatTok n)        = printName n
printLitTok (StrTok n)          = "\"" <> string hardline n <> "\""
printLitTok (StrRawTok n m)     = let pad = pretty (replicate m '#')
                                  in "r" <> pad <> "\"" <> string hardline n <> "\"" <> pad
printLitTok (ByteStrTok n)      = "b\"" <> string hardline n <> "\""
printLitTok (ByteStrRawTok n m) = let pad = pretty (replicate m '#') in "br" <> pad <> "\"" <> string hardline n <> "\"" <> pad

-- | Print a nonterminal
printNonterminal :: Nonterminal a -> Doc a
printNonterminal (NtItem item) = printItem item
printNonterminal (NtBlock blk) = printBlock blk
printNonterminal (NtStmt stmt) = printStmt stmt
printNonterminal (NtPat pat) = printPat pat
printNonterminal (NtExpr expr) = printExpr expr
printNonterminal (NtTy ty) = printType ty
printNonterminal (NtIdent ident) = printIdent ident
printNonterminal (NtPath path) = printPath path False
printNonterminal (NtTT tt) = printTt tt
printNonterminal (NtArm arm) = printArm arm
printNonterminal (NtImplItem item) = printImplItem item
printNonterminal (NtTraitItem item) = printTraitItem item
printNonterminal (NtGenerics generics) = printGenerics generics
printNonterminal (NtWhereClause clause) = printWhereClause True clause
printNonterminal (NtArg arg) = printArg arg True
printNonterminal (NtLit lit) = printLit lit

-- | Print a statement (@print_stmt@)
printStmt :: Stmt a -> Doc a
printStmt (ItemStmt item x)   = annotate x (printItem item)
printStmt (NoSemi expr x)     = annotate x (printExprOuterAttrStyle expr False <> when (requiresSemi expr) ";")
  where
  -- @parse::classify::expr_requires_semi_to_be_stmt@
  requiresSemi :: Expr a -> Bool
  requiresSemi If{} = False
  requiresSemi IfLet{} = False
  requiresSemi While{} = False
  requiresSemi WhileLet{} = False
  requiresSemi ForLoop{} = False
  requiresSemi Loop{} = False
  requiresSemi Match{} = False
  requiresSemi BlockExpr{} = False
  requiresSemi _ = True
printStmt (Semi expr x)       = annotate x (printExprOuterAttrStyle expr False <> ";")
printStmt (MacStmt m ms as x) = annotate x (printOuterAttrs as </> printMac delim m <> end)
  where delim = case ms of { BracesMac -> Brace; _ -> Paren }
        end   = case ms of { SemicolonMac -> ";"; _ -> mempty }
printStmt (Local p ty i as x) = annotate x (printOuterAttrs as <#> group ("let" <+> binding <+> initializer <> ";"))
  where binding = group (printPat p <> perhaps (\t -> ":" <#> indent n (printType t)) ty)
        initializer = perhaps (\e -> "=" <#> indent n (printExpr e)) i

-- | Print an expression
printExpr :: Expr a -> Doc a
printExpr expr = printExprOuterAttrStyle expr True

-- | Print an expression (@print_expr_outer_attr_style@)
-- Inlined @print_expr_in_place@, @print_expr_call@, @print_expr_method_call@, @print_expr_tup@,
-- @print_expr_binary@, @print_expr_unary@, @print_expr_addr_of@, @print_if@, @print_if_let@,
-- @print_expr_repeat@
--
-- TODO: attributes on chained method calls
printExprOuterAttrStyle :: Expr a -> Bool -> Doc a
printExprOuterAttrStyle expr isInline = glue (printEitherAttrs (expressionAttrs expr) Outer isInline) $
  case expr of
    Box _ e x                   -> annotate x ("box" <+> printExpr e)
    InPlace _ place e x         -> annotate x (hsep [ printExpr place, "<-", printExpr e ])
    Vec as exprs x              -> annotate x (block Bracket True "," (printInnerAttrs as) (printExpr <$> exprs))
    Call _ func [arg] x         -> annotate x (printExpr func <> parens (printExpr arg))
    Call _ func args x          -> annotate x (printExpr func <> block Paren True "," mempty (printExpr <$> args))
    MethodCall{}                -> chainedMethodCalls expr False id
    TupExpr as [e] x            -> annotate x (block Paren True ""  (printInnerAttrs as) [ printExpr e <> "," ])
    TupExpr as es x             -> annotate x (block Paren True "," (printInnerAttrs as) (printExpr <$> es))
    Binary _ op lhs rhs x       -> annotate x (hsep [ printExpr lhs, printBinOp op, printExpr rhs ])
    Unary _ op e x              -> annotate x (printUnOp op <> printExpr e)
    Lit _ lit x                 -> annotate x (printLit lit)
    Cast _ e ty x               -> let f = case e of { Cast{} -> printExpr; _ -> printExpr }
                                   in annotate x (hsep [ f e, "as", printType ty ])
    TypeAscription _ e ty x     -> annotate x (printExpr e <> ":" <+> printType ty)
    If _ test blk els x         -> annotate x (hsep [ "if", printExpr test, printBlock blk, printElse els ])
    IfLet _ pats e blk els x    -> annotate x (hsep [ "if let", printPats pats, "=", printExpr e, printBlock blk, printElse els ])
    While as test blk lbl x     -> annotate x (hsep [ printLbl lbl, "while", printExpr test, printBlockWithAttrs True blk as ])
    WhileLet as ps e blk lbl x  -> annotate x (hsep [ printLbl lbl, "while let", printPats ps, "=", printExpr e, printBlockWithAttrs True blk as ])
    ForLoop as pat e blk lbl x  -> annotate x (hsep [ printLbl lbl, "for", printPat pat, "in", printExpr e, printBlockWithAttrs True blk as ])
    Loop as blk lbl x           -> annotate x (hsep [ printLbl lbl, "loop", printBlockWithAttrs True blk as ])
    Match as e arms x           -> annotate x (hsep [ "match", printExpr e, block Brace False "," (printInnerAttrs as) (printArm `map` arms) ])
    Closure _ s cap decl body x -> annotate x (hsep [ when (s == Immovable) "static"
                                                    , when (cap == Value) "move"
                                                    , printFnBlockArgs decl <+> printExpr body])
    BlockExpr attrs blk x       -> annotate x (printBlockWithAttrs True blk attrs)
    Catch attrs blk x           -> annotate x ("do catch" <+> printBlockWithAttrs True blk attrs)
    Assign _ lhs rhs x          -> annotate x (hsep [ printExpr lhs, "=", printExpr rhs ])
    AssignOp _ op lhs rhs x     -> annotate x (hsep [ printExpr lhs, printBinOp op <> "=", printExpr rhs ])
    FieldAccess{}               -> chainedMethodCalls expr False id
    TupField{}                  -> chainedMethodCalls expr False id
    Index{}                     -> chainedMethodCalls expr False id
    Range _ start end limits x  -> annotate x (perhaps printExpr start <+> printRangeLimits limits <+> perhaps printExpr end)
    PathExpr _ Nothing path x   -> annotate x (printPath path True)
    PathExpr _ (Just qs) path x -> annotate x (printQPath path qs True)
    AddrOf _ mut e x            -> annotate x ("&" <> printMutability mut <+> printExpr e)
    Break _ brk e x             -> annotate x ("break" <+> printLbl' brk <+> perhaps printExpr e)
    Continue _ cont x           -> annotate x ("continue" <+> printLbl' cont)
    Ret _ result x              -> annotate x ("return" <+> perhaps printExpr result)
    Yield _ result x            -> annotate x ("yield" <+> perhaps printExpr result)
    MacExpr _ m x               -> annotate x (printMac Paren m)
    Struct as p fs Nothing x    -> annotate x (printPath p True <+> block Brace True "," (printInnerAttrs as) (printField `map` fs))
    Struct as p fs (Just d) x   -> let body = [ printField f <> "," | f <- fs ] ++ [ ".." <> printExpr d ] 
                                   in annotate x (printPath p True <+> block Brace True mempty (printInnerAttrs as) body)
    Repeat attrs e cnt x        -> annotate x (brackets (printInnerAttrs attrs <+> printExpr e <> ";" <+> printExpr cnt))
    ParenExpr attrs e x         -> annotate x (parens (printInnerAttrs attrs <+> printExpr e))
    Try{}                       -> chainedMethodCalls expr False id
  where
  printLbl = perhaps (\(Label n x) -> annotate x ("'" <> printName n) <> ":")
  printLbl' = perhaps (\(Label n x) -> annotate x ("'" <> printName n))
  glue = if isInline then (<+>) else (</>)

  -- | From an expression, seperate the first non-method-call from the list of method call suffixes
  --
  -- Collects in the list
  --   * methods
  --   * field accesses
  --
  -- Allows interweaving
  --   * tuple field accesses
  --   * array indexes
  --   * try
  --
  chainedMethodCalls :: Expr a            -- ^ expression
                     -> Bool              -- ^ last expression was a 'TupField' (if we have two
                                          -- successive 'TupField's, we need a space between them
                                          -- to prevent them from looking like a float literal)
                     -> (Doc a -> Doc a)  -- ^ suffix to the expression
                     -> Doc a
  chainedMethodCalls (MethodCall _ s i ts' as x) _ fdoc
    = let tys = perhaps (\ts -> "::<" <> commas ts printType <> ">") ts'
          as' = case as of
                  [a] -> parens (printExpr a)
                  _ -> block Paren True "," mempty (printExpr <$> as)
      in chainedMethodCalls s False (annotate x . (<##> fdoc (indent n (hcat [ ".", printIdent i, tys, as' ]))))
  chainedMethodCalls (FieldAccess _ s i x) _ fdoc
    = chainedMethodCalls s False (annotate x . (<##> fdoc (indent n (hcat [ ".", printIdent i ]))))
  chainedMethodCalls (Try _ s x) _ fdoc
    = chainedMethodCalls s False (annotate x . (<> fdoc "?"))
  chainedMethodCalls (Index _ s i x) _ fdoc
    = chainedMethodCalls s False (annotate x . (<> fdoc ("[" <> block NoDelim True mempty mempty [printExpr i] <> "]")))
  chainedMethodCalls (TupField _ s i x) t fdoc
    = chainedMethodCalls s True (annotate x . (<> fdoc ("." <> pretty i <> when t " ")))
  chainedMethodCalls e _ fdoc = group (fdoc (printExpr e))

-- | Print a string literal
printStr :: StrStyle -> String -> Doc a
printStr sty str = unAnnotate (printLit (Str str sty Unsuffixed ()))

-- | Extract from an expression its attributes
expressionAttrs :: Expr a -> [Attribute a]
expressionAttrs (Box as _ _) = as
expressionAttrs (InPlace as _  _ _) = as
expressionAttrs (Vec as _ _) = as
expressionAttrs (Call as _  _ _) = as
expressionAttrs (MethodCall as _ _  _ _ _) = as
expressionAttrs (TupExpr as _ _) = as
expressionAttrs (Binary as _ _ _ _) = as
expressionAttrs (Unary as _ _ _) = as
expressionAttrs (Lit as _ _) = as
expressionAttrs (Cast as _ _ _) = as
expressionAttrs (TypeAscription as _  _ _) = as
expressionAttrs (If as _ _ _ _) = as
expressionAttrs (IfLet as _ _ _ _ _) = as
expressionAttrs (While as _  _ _ _) = as
expressionAttrs (WhileLet as _ _ _ _ _) = as
expressionAttrs (ForLoop as _ _ _ _ _) = as
expressionAttrs (Loop as _ _ _) = as
expressionAttrs (Match as _ _ _) = as
expressionAttrs (Closure as _ _ _ _ _) = as
expressionAttrs (BlockExpr as _ _) = as
expressionAttrs (Catch as _ _) = as
expressionAttrs (Assign as _ _ _) = as
expressionAttrs (AssignOp as _ _ _ _) = as
expressionAttrs (FieldAccess as _ _ _) = as
expressionAttrs (TupField as _ _ _) = as
expressionAttrs (Index as _ _ _) = as
expressionAttrs (Range as _ _ _ _) = as
expressionAttrs (PathExpr as _ _ _) = as
expressionAttrs (AddrOf as _ _ _) = as
expressionAttrs (Break as _ _ _) = as
expressionAttrs (Continue as _ _) = as
expressionAttrs (Ret as _ _) = as
expressionAttrs (MacExpr as _ _) = as
expressionAttrs (Struct as _ _ _ _) = as
expressionAttrs (Repeat as _ _ _) = as
expressionAttrs (ParenExpr as _ _) = as
expressionAttrs (Try as _ _) = as
expressionAttrs (Yield as _ _) = as

-- | Print a field
printField :: Field a -> Doc a
printField (Field ident Nothing x) = annotate x (printIdent ident)
printField (Field ident (Just expr) x) = annotate x (printIdent ident <>":" <+> printExpr expr)

-- | Print range limits
printRangeLimits :: RangeLimits -> Doc a
printRangeLimits HalfOpen = ".."
printRangeLimits Closed = "..="

-- | Print a closure function declaration (@print_fn_block_args@)
printFnBlockArgs :: FnDecl a -> Doc a
printFnBlockArgs (FnDecl args ret _ x) = annotate x ("|" <> args' <> "|" <+> ret')
  where ret' = perhaps (\ty -> "->" <+> printType ty) ret
        args' = commas args (`printArg` True)

-- | Print the arm of a match expression (@print_arm@)
printArm :: Arm a -> Doc a
printArm (Arm as pats guard body x) = annotate x $ printOuterAttrs as
  </> printPats pats 
  <+> perhaps (\e -> "if" <+> printExpr e) guard
  <+> "=>"
  <+> printExpr body

printPats :: Foldable f => f (Pat a) -> Doc a
printPats pats = group (foldr1 (\a b -> a <+> "|" <#> b) (printPat `map` toList pats))

-- | Print a block
printBlock :: Block a -> Doc a
printBlock blk = printBlockWithAttrs True blk []

-- | Print a block with attributes (@print_block_with_attrs@ or @print_block_maybe_unclosed@)
printBlockWithAttrs :: Bool -> Block a -> [Attribute a] -> Doc a
printBlockWithAttrs b (Block stmts rules x) as = annotate x (printUnsafety rules <+> block Brace b mempty (printInnerAttrs as) stmts')
  where
  stmts' | null stmts = []
         | otherwise = body ++ [ lastStmt ]

  body = printStmt `map` Prelude.init stmts
  
  lastStmt = case last stmts of
               NoSemi expr _ -> printExprOuterAttrStyle expr False
               stmt -> printStmt stmt

-- | Print an @else@ expression (@print_else@)
printElse :: Maybe (Expr a) -> Doc a
printElse Nothing = mempty
printElse (Just (If _ e t s x))      = annotate x (hsep [ "else if", printExpr e, printBlock t, printElse s ])
printElse (Just (IfLet _ p e t s x)) = annotate x (hsep [ "else if let", printPats p, "=", printExpr e, printBlock t, printElse s ])
printElse (Just (BlockExpr _ blk x)) = annotate x (hsep [ "else", printBlock blk ])
printElse _ = error "printElse saw `if` with a weird alternative"

-- | Print a binary operator 
printBinOp :: BinOp -> Doc a
printBinOp AddOp = "+"
printBinOp SubOp = "-"
printBinOp MulOp = "*"
printBinOp DivOp = "/"
printBinOp RemOp = "%"
printBinOp AndOp = "&&"
printBinOp OrOp = "||"
printBinOp BitXorOp = "^"
printBinOp BitAndOp = "&"
printBinOp BitOrOp = "|"
printBinOp ShlOp = "<<"
printBinOp ShrOp = ">>"
printBinOp EqOp = "=="
printBinOp LtOp = "<"
printBinOp LeOp = "<="
printBinOp NeOp = "!="
printBinOp GeOp = ">="
printBinOp GtOp = ">"

-- | Print a unary operator
printUnOp :: UnOp -> Doc a
printUnOp Deref = "*"
printUnOp Not = "!"
printUnOp Neg = "-" 

-- | Print inner attributes (@print_inner_attributes@ or @print_inner_attributes_inline@
-- or @print_inner_attributes_nodbreak@ - distinction has to be made at callsite
-- whether to force newline)
printInnerAttrs :: [Attribute a] -> Doc a
printInnerAttrs attrs = printEitherAttrs attrs Inner False

-- | Print outer attributes (@print_outer_attributes@ or @print_outer_attributes_no_trailing_hardbreak@)
printOuterAttrs :: [Attribute a] -> Doc a
printOuterAttrs attrs = printEitherAttrs attrs Outer False

-- | Print either type of attributes (@print_either_attributes@)
printEitherAttrs :: [Attribute a] -> AttrStyle -> Bool -> Doc a
printEitherAttrs attrs kind inline = unless (null attrs') (glue attrs')
  where glue = if inline then hsep else vcat
        attrs' = [ printAttr attr inline | attr <- attrs, style attr == kind ]

        style (Attribute sty _ _ _) = sty
        style (SugaredDoc sty _ _ _) = sty

-- | Print an attribute (@print_attribute_inline@ or @print_attribute@)
printAttr :: Attribute a -> Bool -> Doc a
printAttr (Attribute Inner p ts x) _     = annotate x ("#![" <> printPath p True <> printTokenStreamSp ts <> printTokenStream ts <> "]")
printAttr (Attribute Outer p ts x) _     = annotate x ("#[" <> printPath p True <> printTokenStreamSp ts <> printTokenStream ts <> "]")
printAttr (SugaredDoc Inner True c x)  _ = annotate x (noIndent ("/*!" <> string hardline c <> "*/"))
printAttr (SugaredDoc Outer True c x)  _ = annotate x (noIndent ("/**" <> string hardline c <> "*/"))
printAttr (SugaredDoc Inner False c x) _ = annotate x (flatAlt ("//!" <> pretty c) ("/*!" <> pretty c <> "*/"))
printAttr (SugaredDoc Outer False c x) _ = annotate x (flatAlt ("///" <> pretty c) ("/**" <> pretty c <> "*/"))

-- | Print an identifier as is, or as cooked string if containing a hyphen
printCookedIdent :: Ident -> Doc a
printCookedIdent ident@(Ident str raw _)
  | '-' `elem` str && not raw = printStr Cooked str
  | otherwise = printIdent ident 


-- | Print an item (@print_item@)
printItem :: Item a -> Doc a
printItem (ExternCrate as vis ident p x) = annotate x $ align $ printOuterAttrs as <#>
  hsep [ printVis vis, "extern", "crate", perhaps (\p' -> printCookedIdent p' <+> "as") p, printIdent ident <> ";" ]

printItem (Use as vis ut x) = annotate x $ align $ printOuterAttrs as <#>
  hsep [ printVis vis, "use", printUseTree ut <> ";" ]

printItem (Static as vis ident ty m e x) = annotate x $ align $ printOuterAttrs as <#>
  hsep [ printVis vis, "static", printMutability m, printIdent ident <> ":", printType ty, "=", printExpr e <> ";" ]

printItem (ConstItem as vis ident t e x) = annotate x $ align $ printOuterAttrs as <#>
  hsep [ printVis vis, "const", printIdent ident <> ":", printType t, "=", printExpr e <> ";" ]

printItem (Fn as vis ident d s c a t b x) = annotate x $ align $ printOuterAttrs as <#>
  printFn d s c a (Just ident) t vis (Just (b, as))

printItem (Mod as vis ident items x) = annotate x $ align $ printOuterAttrs as <#>
  hsep [ printVis vis, "mod", printMod ident items as ]

printItem (ForeignMod as vis a i x) = annotate x $ align $ printOuterAttrs as <#>
  hsep [ printVis vis, printAbi a, printForeignMod i as ]

printItem (TyAlias as vis ident ty ps x) = annotate x $ align $ printOuterAttrs as <#>
  let wc = printWhereClause True (whereClause ps)
      leading = printVis vis <+> "type" <+> printIdent ident <> printGenerics ps
  in emptyElim (group (leading <+> "=" <#> indent n (printType ty <> ";")))
               (\w -> vsep [leading, w, "=" <+> printType ty <> ";"])
               wc

printItem (Enum as vis ident vars ps x) = annotate x $ align $ printOuterAttrs as <#>
  printEnumDef vars ps ident vis

printItem (StructItem as vis ident s g x) = annotate x $ align $ printOuterAttrs as <#>
  hsep [ printVis vis, "struct", printStruct s g ident True True ]

printItem (Union as vis ident s g x) = annotate x $ align $ printOuterAttrs as <#>
  hsep [ printVis vis, "union", printStruct s g ident True True ]

printItem (Trait as vis ident a u g tys i x) = annotate x $ align $ printOuterAttrs as <#> 
  let leading = hsep [ printVis vis, printUnsafety u, when a "auto", "trait"
                     , printIdent ident <> printGenerics g <> printBounds ":" tys
                     ]
      lagging = block Brace False mempty (printInnerAttrs as) (printTraitItem `map` i)
      wc = printWhereClause True (whereClause g)
  in emptyElim (leading <+> lagging)
               (\w -> vsep [leading, w, lagging])
               wc

printItem (TraitAlias as vis ident g bds x) = annotate x $ align $ printOuterAttrs as <#>
  let leading = printVis vis <+> "trait" <+> printIdent ident <> printGenerics g
  in group (leading <#> indent n (printBounds "=" (toList bds)) <> ";")

printItem (Impl as vis d u p g t ty i x) = annotate x $ align $ printOuterAttrs as <#> 
  let generics = case g of { Generics [] [] _ _ -> mempty; _ -> printGenerics g }
      traitref = perhaps (\t' -> printPolarity p <> printTraitRef t' <+> "for") t
      leading = hsep [ printVis vis, printDef d, printUnsafety u
                     , "impl" <> generics, traitref, printType ty
                     ]
      lagging = block Brace False mempty (printInnerAttrs as) (printImplItem `map` i)
      wc = printWhereClause True (whereClause g)
  in emptyElim (leading <+> lagging)
               (\w -> vsep [leading, w, lagging])
               wc

printItem (MacItem as i (Mac p ts y) x) = annotate x $ annotate y $ align $ printOuterAttrs as <#>
  (printPath p True <> "!" <+> perhaps printIdent i <+> block Brace True mempty mempty [ printTokenStream ts ])

printItem (MacroDef as i ts x) = annotate x $ align $ printOuterAttrs as <#>
  ("macro_rules" <> "!" <+> printIdent i <+> block Brace True mempty mempty [ printTokenStream ts ])


-- | Print a trait item (@print_trait_item@)
printTraitItem :: TraitItem a -> Doc a
printTraitItem (ConstT as ident ty expr x) = annotate x $ printOuterAttrs as <#> printAssociatedConst ident ty expr InheritedV
printTraitItem (MethodT as ident g sig body x) = annotate x $ printOuterAttrs as <#> printMethodSig ident g sig InheritedV (fmap (\b -> (b, as)) body)
printTraitItem (TypeT as ident bounds ty x) = annotate x $ printOuterAttrs as <#> printAssociatedType ident (Just bounds) ty
printTraitItem (MacroT as m x) = annotate x $ printOuterAttrs as <#> printMac Paren m <> ";"

-- | Print type parameter bounds with the given prefix, but only if there are any bounds (@print_bounds@)
printBounds :: Doc a -> [TyParamBound a] -> Doc a
printBounds _ [] = mempty
printBounds prefix bs = group (prefix <#> ungroup (block NoDelim False " +" mempty (printBound `map` bs)))

-- | Print a type parameter bound
printBound :: TyParamBound a -> Doc a
printBound (RegionTyParamBound lt x) = annotate x $ printLifetime lt
printBound (TraitTyParamBound tref modi x) = annotate x $ when (modi == Maybe) "?" <> printPolyTraitRef tref

-- | Print the formal lifetime list (@print_formal_lifetime_list@)
printFormalLifetimeList :: [LifetimeDef a] -> Doc a
printFormalLifetimeList [] = mempty
printFormalLifetimeList defs = "for" <> angles (align (fillSep (punctuate "," (printDef `map` defs))))
  where printDef (LifetimeDef as lt bds x) = annotate x (printOuterAttrs as <+> flatten (printLifetimeBounds lt bds))

-- | Print an impl item (@print_impl_item@)
printImplItem :: ImplItem a -> Doc a
printImplItem (ConstI as vis def ident ty expr x) = annotate x $ printOuterAttrs as <#>
  (printVis vis <+> printDef def <+> printAssociatedConst ident ty (Just expr) InheritedV)
printImplItem (MethodI as vis def ident g sig body x) = annotate x $ printOuterAttrs as <#>
  (printVis vis <+> printDef def <+> printMethodSig ident g sig InheritedV (Just (body, as)))
printImplItem (TypeI as vis def ident ty x) = annotate x $ printOuterAttrs as <#>
  (printVis vis <+> printDef def <+> printAssociatedType ident Nothing (Just ty))
printImplItem (MacroI as def mac x) = annotate x $ printOuterAttrs as <#>
  (printDef def <+> printMac Paren mac <> ";")

-- | Print defaultness (@Defaultness@)
printDef :: Defaultness -> Doc a
printDef Default = "default"
printDef Final = mempty 

-- | Print an associated type (@printAssociatedType@)
printAssociatedType :: Ident ->  Maybe [TyParamBound a] -> Maybe (Ty a) -> Doc a
printAssociatedType ident bounds_m ty_m = "type" <+> (printIdent ident
  <> perhaps (printBounds ":") bounds_m)
  <+> perhaps (\ty -> "=" <+> printType ty) ty_m
  <> ";"

-- | Print a method signature (@print_method_sig@)
printMethodSig :: Ident -> Generics a -> MethodSig a -> Visibility a -> Maybe (Block a, [Attribute a]) -> Doc a
printMethodSig ident generics (MethodSig unsafety constness abi decl)
  = printFn decl unsafety constness abi (Just ident) generics

-- | Print an associated constant (@print_associated_const@)
printAssociatedConst :: Ident -> Ty a -> Maybe (Expr a) -> Visibility a -> Doc a
printAssociatedConst ident ty default_m vis = printVis vis
  <+> "const" <+> printIdent ident <> ":" <+> printType ty
  <+> perhaps (\expr -> "=" <+> printExpr expr) default_m
  <> ";"

-- | Print the polarity of a trait
printPolarity :: ImplPolarity -> Doc a
printPolarity Negative = "!"
printPolarity Positive = mempty

-- | Print visibility (@print_visibility@)
printVis :: Visibility a -> Doc a
printVis PublicV = "pub"
printVis CrateV = "pub(crate)"
printVis (RestrictedV path@(Path False [PathSegment "super" Nothing _] _)) = "pub(" <> printPath path False <> ")"
printVis (RestrictedV path@(Path False [PathSegment "self" Nothing _] _)) = "pub(" <> printPath path False <> ")"
printVis (RestrictedV path) = "pub(" <> "in" <+> printPath path False <> ")"
printVis InheritedV = mempty

-- | Print a foreign item (@print_foreign_item@)
printForeignItem :: ForeignItem a -> Doc a
printForeignItem (ForeignFn attrs vis ident decl generics x) = annotate x $
  printOuterAttrs attrs <#> printFn decl Normal NotConst Rust (Just ident) generics vis Nothing
printForeignItem (ForeignStatic attrs vis ident ty mut x) = annotate x $
  printOuterAttrs attrs <#> printVis vis <+> "static" <+> printMutability mut <+> printIdent ident <> ":" <+> printType ty <> ";"
printForeignItem (ForeignTy attrs vis ident x) = annotate x $
  printOuterAttrs attrs <#> printVis vis <+> "type" <+> printIdent ident <> ";"

-- | Print a struct definition (@print_struct@)
printStruct :: VariantData a -> Generics a -> Ident -> Bool -> Bool -> Doc a
printStruct structDef generics ident printFinalizer annotateGenerics =
  printIdent ident <> gen
    <> case (structDef, whereClause generics) of 
          (StructD fields x, WhereClause [] _) -> annotate x $ space <> block Brace False "," mempty (printStructField `map` fields)
          (StructD fields x, wc) -> annotate x $ line <> printWhereClause True wc <#> block Brace False "," mempty (printStructField `map` fields)
          (TupleD fields x, WhereClause [] _) -> annotate x $ block Paren True "," mempty (printStructField `map` fields) <> when printFinalizer ";" 
          (TupleD fields x, wc) -> annotate x $ block Paren True "," mempty (printStructField `map` fields) <#> printWhereClause (not printFinalizer) wc <> when printFinalizer ";" 
          (UnitD x, WhereClause [] _) -> annotate x $ when printFinalizer ";"
          (UnitD x, wc) -> annotate x $ line <> printWhereClause (not printFinalizer) wc <> when printFinalizer ";"
  where gen = if annotateGenerics then printGenerics generics else unAnnotate (printGenerics generics)

-- | Print a struct field
printStructField :: StructField a -> Doc a
printStructField (StructField i v t as x) = annotate x (printOuterAttrs as <#> hsep [ printVis v, perhaps (\i' -> printIdent i' <> ":") i, printType t ])

-- | Pretty print unsafety (@print_unsafety@)
printUnsafety :: Unsafety -> Doc a
printUnsafety Normal = mempty
printUnsafety Unsafe = "unsafe"

-- | Print an enum definition (@print_enum_def@)
printEnumDef :: [Variant a] -> Generics a -> Ident -> Visibility a -> Doc a
printEnumDef variants generics ident vis =
   case whereClause generics of
     WhereClause [] _ -> leading <+> lagging
     wc -> leading <#> printWhereClause True wc <#> lagging
  where leading = printVis vis <+> "enum" <+> (printIdent ident <> printGenerics generics)
        lagging = block Brace False "," mempty [ printOuterAttrs as <#> printVariant v | v@(Variant _ as _ _ _) <- variants ]

-- | Print a variant (@print_variant@)
printVariant :: Variant a -> Doc a
printVariant (Variant i _ _data e x) = annotate x (body <+> disc)
  where body = printStruct _data (Generics [] [] (WhereClause [] undefined) undefined) i False False
        disc = perhaps (\e' -> "=" <+> printExpr e') e

-- | Print a where clause (@print_where_clause@). The 'Bool' argument indicates whether to have a
-- trailing comma or not.
printWhereClause :: Bool -> WhereClause a -> Doc a
printWhereClause trailing (WhereClause predicates x)
  | null predicates = mempty
  | trailing = annotate x ("where" <#> block NoDelim False "," mempty (printWherePredicate `map` predicates))
  | otherwise = annotate x ("where" <#> block NoDelim False mempty mempty (punctuate "," (printWherePredicate `map` predicates)))

-- | Print a where clause predicate
printWherePredicate :: WherePredicate a -> Doc a
printWherePredicate (BoundPredicate blt ty bds y) = annotate y (printFormalLifetimeList blt <+> printType ty <> printBounds ":" bds)
printWherePredicate (RegionPredicate lt bds y) = annotate y (printLifetimeBounds lt bds)
printWherePredicate (EqPredicate lhs rhs y) = annotate y (printType lhs <+> "=" <+> printType rhs)

-- | Print a function (@print_fn@)
printFn :: FnDecl a -> Unsafety -> Constness -> Abi -> Maybe Ident -> Generics a -> Visibility a -> Maybe (Block a, [Attribute a]) -> Doc a
printFn decl unsafety constness abi name generics vis blkAttrs =
  printFnHeaderInfo unsafety constness abi vis
    <+> perhaps printIdent name
    <> printGenerics generics
    <> printFnArgsAndRet decl
    <+#> whereBlk
  where
  ((<+#>), whereBlk) = case (whereClause generics, blkAttrs) of
                         (WhereClause [] _, Nothing) -> ((<>), ";")
                         (WhereClause [] _, Just (blk, attrs)) -> ((<+>), printBlockWithAttrs False blk attrs)
                         (wc,               Nothing) -> ((<#>), printWhereClause False wc <> ";")
                         (wc,               Just (blk, attrs)) -> ((<#>), printWhereClause True wc <#> printBlockWithAttrs False blk attrs)


-- | Print the function arguments and the return type (@print_fn_args_and_ret@)
printFnArgsAndRet :: FnDecl a -> Doc a
printFnArgsAndRet (FnDecl args ret var x)
  | var = annotate x (block Paren True mempty mempty ([ printArg a False <> "," | a <- args ] ++ [ "..." ]) <+> ret')
  | otherwise = annotate x (block Paren True "," mempty [ printArg a False | a <- args ] <+> ret')
  where ret' = perhaps (\t -> "->" <+> printType t) ret

-- | Print an argument (@print_arg@)
printArg :: Arg a -> Bool -> Doc a
printArg (Arg (Just pat) (Infer x') x) True = annotate x $ annotate x' (printPat pat)
printArg (Arg Nothing ty x) _ = annotate x (printType ty)
printArg (Arg (Just pat) ty x) _ = annotate x (printPat pat <> ":" <+> printType ty)
printArg (SelfValue mut x) _ = annotate x (printMutability mut <+> "self")
printArg (SelfRegion lt mut x) _ = annotate x ("&" <> hsep [perhaps printLifetime lt, printMutability mut, "self"])
printArg (SelfExplicit ty mut x) _ = annotate x (printMutability mut <+> "self" <> ":" <+> printType ty)

-- | Print a lifetime (@print_lifetime@)
printLifetime :: Lifetime a -> Doc a
printLifetime (Lifetime n x) = annotate x ("'" <> printName n)

-- | Print a lifetime definition
printLifetimeDef :: LifetimeDef a -> Doc a
printLifetimeDef (LifetimeDef as lt bds x) = annotate x (printOuterAttrs as <+> printLifetimeBounds lt bds)

-- | Print mutability (@print_mutability@)
printMutability :: Mutability -> Doc a
printMutability Mutable = "mut"
printMutability Immutable = mempty

-- | Like 'printMutability', but prints @const@ in the immutable case 
printFullMutability :: Mutability -> Doc a
printFullMutability Mutable = "mut"
printFullMutability Immutable = "const"

-- | Print a pattern (@print_pat@)
printPat :: Pat a -> Doc a
printPat (WildP x)                      = annotate x "_"
printPat (IdentP bm p s x)              = annotate x (printBindingMode bm <+> printIdent p <+> perhaps (\p' -> "@" <+> printPat p') s)
printPat (StructP p fs False x)         = annotate x (printPath p True <+> block Brace True "," mempty (printFieldPat `map` fs))
printPat (StructP p fs True x)          = annotate x (printPath p True <+> block Brace True mempty mempty ([ printFieldPat f <> "," | f <- fs ] ++ [ ".." ]))
printPat (TupleStructP p es Nothing x)  = annotate x (printPath p True <> "(" <> commas es printPat <> ")")
printPat (TupleStructP p es (Just d) x) = let (before,after) = splitAt d es
  in annotate x (printPath p True <> "(" <> commas before printPat <> when (d /= 0) ","
                    <+> ".." <> when (d /= length es) ("," <+> commas after printPat) <> ")")
printPat (PathP Nothing path x)         = annotate x (printPath path True)
printPat (PathP (Just qself) path x)    = annotate x (printQPath path qself True)
printPat (TupleP [elt] Nothing x)        = annotate x ("(" <> printPat elt <> ",)")
printPat (TupleP elts Nothing x)        = annotate x ("(" <> commas elts printPat <> ")")
printPat (TupleP elts (Just ddpos) _) = let (before,after) = splitAt ddpos elts
  in "(" <> commas before printPat <> unless (null before) ","
      <+> ".." <> unless (null after) ("," <+> commas after printPat) <> ")"
printPat (BoxP inner x)                 = annotate x ("box" <+> printPat inner)
printPat (RefP inner mutbl x)           = annotate x ("&" <> printMutability mutbl <+> printPat inner)
printPat (LitP expr x)                  = annotate x (printExpr expr)
printPat (RangeP lo hi x)               = annotate x (printExpr lo <+> "..=" <+> printExpr hi)
printPat (SliceP pb Nothing pa x)       = annotate x ("[" <> commas (pb ++ pa) printPat <> "]")
printPat (SliceP pb (Just ps) pa x)     = annotate x ("[" <> commas pb printPat <> ps' <+> commas pa printPat <> "]")
  where ps' = hcat [ unless (null pb) ","
                   , space
                   , case ps of WildP{} -> mempty
                                _ -> printPat ps
                   , ".."
                   , unless (null pa) ","
                   ]
printPat (MacP m x)                     = annotate x (printMac Paren m)

-- | Print a field pattern
printFieldPat :: FieldPat a -> Doc a
printFieldPat (FieldPat i p x) = annotate x (perhaps (\i -> printIdent i <> ":") i <+> printPat p)

-- | Print a binding mode
printBindingMode :: BindingMode -> Doc a
printBindingMode (ByRef mutbl) = "ref" <+> printMutability mutbl
printBindingMode (ByValue Immutable) = mempty
printBindingMode (ByValue Mutable) = "mut"

-- | Print the prefix of a function - all the stuff up to and including @fn@ (@print_fn_header_info@)
printFnHeaderInfo :: Unsafety -> Constness -> Abi -> Visibility a -> Doc a
printFnHeaderInfo u c a v = hsep [ printVis v, case c of { Const -> "const"; _ -> mempty }
                                 , printUnsafety u, printAbi a, "fn" ]

-- | Print the ABI
printAbi :: Abi -> Doc a
printAbi Rust = mempty
printAbi abi = "extern" <+> "\"" <> root abi <> "\""
  where
    root :: Abi -> Doc a
    root Cdecl = "cdecl"
    root Stdcall = "stdcall"
    root Fastcall = "fastcall"
    root Vectorcall = "vectorcall"
    root Aapcs = "aapcs"
    root Win64 = "win64"
    root SysV64 = "sysv64"
    root PtxKernel = "ptx-kernel"
    root Msp430Interrupt = "msp430-interrupt"
    root X86Interrupt = "x86-interrupt"
    root Rust = "Rust"
    root C = "C"
    root System = "system"
    root RustIntrinsic = "rust-intrinsic"
    root RustCall = "rust-call"
    root PlatformIntrinsic = "platform-intrinsic"
    root Unadjusted = "unadjusted"

 
-- | Print the interior of a module given the list of items and attributes in it (@print_mod@)
printMod :: Ident -> Maybe [Item a] -> [Attribute a] -> Doc a
printMod i (Just items) attrs = printIdent i <+> block Brace False mempty (printInnerAttrs attrs) (punctuate line' (printItem `map` items))
printMod i Nothing _ = printIdent i <> ";"

-- | Print the interior of a foreign module given the list of items and attributes in it (@print_mod@)
printForeignMod :: [ForeignItem a] -> [Attribute a] -> Doc a
printForeignMod items attrs = block Brace False mempty (printInnerAttrs attrs) (printForeignItem `map` items)

-- | Print generics (@print_generics@)
-- Remark: we are discarding the where clause because it gets printed seperately from the rest of
-- the generic.
printGenerics :: Generics a -> Doc a
printGenerics (Generics lifetimes tyParams _ x)
  | null lifetimes && null tyParams = mempty
  | otherwise =  let lifetimes' = printLifetimeDef `map` lifetimes
                     bounds' = [ printTyParam param | param<-tyParams ]
                 in annotate x (group ("<" <##> ungroup (block NoDelim True "," mempty (lifetimes' ++ bounds')) <##> ">"))

-- | Print a poly-trait ref (@print_poly_trait_ref@)
printPolyTraitRef :: PolyTraitRef a -> Doc a
printPolyTraitRef (PolyTraitRef lts tref x) = annotate x (printFormalLifetimeList lts <+> printTraitRef tref)

-- | Print a trait ref (@print_trait_ref@)
printTraitRef :: TraitRef a -> Doc a
printTraitRef (TraitRef path) = printPath path False

-- | Print a path parameter and signal whether its generic (if any) should start with a colon (@print_path_parameters@)
printPathParameters :: PathParameters a -> Bool -> Doc a
printPathParameters (Parenthesized ins out x) colons = annotate x $
  when colons "::" <> parens (commas ins printType) <+> perhaps (\t -> "->" <+> printType t) out
printPathParameters (AngleBracketed lts tys bds x) colons = annotate x (when colons "::" <> "<" <> hsep (punctuate "," (lts' ++ tys' ++ bds')) <> ">")
  where
    lts' = printLifetime <$> lts
    tys' = printType <$> tys
    bds' = (\(ident,ty) -> printIdent ident <+> "=" <+> printType ty) <$> bds


-- | Print a path, specifiying explicitly whether to include colons (@::@) before generics
-- or not (so expression style or type style generics) (@print_path@)
printPath :: Path a -> Bool -> Doc a
printPath (Path global segs x) colons = annotate x (when global "::" <> hcat (punctuate "::" (map (`printSegment` colons) segs)))

-- | Print a path segment
printSegment :: PathSegment a -> Bool -> Doc a
printSegment (PathSegment i ps x) colons = annotate x (printIdent i <> params)
  where params = perhaps (\p -> printPathParameters p colons) ps


-- | Print a qualified path, specifiying explicitly whether to include colons (@::@) before
-- generics or not (so expression style or type style generics) (@print_qpath@)
printQPath :: Path a -> QSelf a -> Bool -> Doc a
printQPath (Path global segs x) (QSelf ty n) colons = hcat [ "<", printType ty <+> aliasedDoc, ">", "::", restDoc ]
  where
  (aliased, rest) = splitAt n segs
  
  aliasedDoc = case aliased of
                 [] -> mempty
                 segs -> "as" <+> printPath (Path global segs x) False

  restDoc = printPath (Path False rest x) colons

-- | Print a use tree
printUseTree :: UseTree a -> Doc a
printUseTree (UseTreeSimple p Nothing x) = annotate x (printPath p True)
printUseTree (UseTreeSimple p (Just i) x) = annotate x (printPath p True <+> "as" <+> printIdent i)
printUseTree (UseTreeGlob p@(Path _ [] _) x) = annotate x (printPath p True <> "*")
printUseTree (UseTreeGlob p x) = annotate x (printPath p True <> "::*")
printUseTree (UseTreeNested p@(Path _ [] _) n x) = annotate x (printPath p True <> "{" <> hcat (punctuate ", " (map printUseTree n)) <> "}")
printUseTree (UseTreeNested p n x) = annotate x (printPath p True <> "::{" <> hcat (punctuate ", " (map printUseTree n)) <> "}")

-- | Print a type parameters (@print_ty_param@)
printTyParam :: TyParam a -> Doc a
printTyParam (TyParam as i bds def x) = annotate x $ hsep
  [ printOuterAttrs as
  , printIdent i <> printBounds ":" bds
  , perhaps (\def' -> "=" <+> printType def') def
  ]

-- | Print lifetime bounds (@print_lifetime_bounds@)
printLifetimeBounds :: Lifetime a -> [Lifetime a] -> Doc a
printLifetimeBounds lifetime bounds = printLifetime lifetime
  <> unless (null bounds) (":" <+> foldr1 (\x y -> x <+> "+" <+> y) (printLifetime <$> bounds))

